

💡 정리
- 다형성이 가장 중요하다!
- 디자인 패턴 대부분은 다형성을 활용하는 것이다!
- 스프링의 핵심인 IoC, DI도 결국 다형성을 활용하는 것이다!
- 스프링을 사용하면 마치 레고 블럭 조립하듯이 구현을 편리하게 변경할 수 있다!
클라이언트 = Driver
서버 = K3Car, Model3Car
package poly.car0;
public class K3Car {
public void startEngine() {
System.out.println("K3Car.startEngine");
}
public void offEngine() {
System.out.println("K3Car.offEngine");
}
public void pressAccelerator() {
System.out.println("K3Car.pressAccelerator");
}
}
package poly.car0;
public class Model3Car {
public void startEngine() {
System.out.println("Model3Car.startEngine");
}
public void offEngine() {
System.out.println("Model3Car.offEngine");
}
public void pressAccelerator() {
System.out.println("Model3Car.pressAccelerator");
}
}
package poly.car0;
public class Driver {
private K3Car k3Car;
private Model3Car model3Car; //추가
public void setK3Car(K3Car k3Car) {
this.k3Car = k3Car;
}
//추가
public void setModel3Car(Model3Car model3Car) {
this.model3Car = model3Car;
}
public void drive() {
System.out.println("자동차를 운전합니다.");
if (k3Car != null) {
k3Car.startEngine();
k3Car.pressAccelerator();
k3Car.offEngine();
} else if (model3Car != null) {
model3Car.startEngine();
model3Car.pressAccelerator();
model3Car.offEngine();
}
}
}
클래스(Driver) 상에서, 다른 클래스를 알고있다면(여기선 K3Car, Model3Car 이다),
다른 클래스를 의존하고 있다고 칭한다. → ⭐ 클라이언트에서는 의존성을 인터페이스에 집중해야한다!
package poly.car0;
public class CarMain0 {
public static void main(String[] args) {
Driver driver = new Driver();
K3Car k3Car = new K3Car();
driver.setK3Car(k3Car);
driver.drive();
//추가
Model3Car model3Car = new Model3Car();
driver.setK3Car(null);
driver.setModel3Car(model3Car);
driver.drive();
}
}
위 코드를 보면, 자동차가 늘어날 수록 Driver의 코드가 엄청나게 변경된다.
심지어 main 메서드에서도 driver가 새로운 자동차를 운전하려면
set 메서드, 운전메서드를 중복으로 사용해야 한다.
package poly.car1;
public interface Car {
void startEngine();
void offEngine();
void pressAccelerator();
}package poly.car1;
public class Model3Car implements Car {
@Override
public void startEngine() {
System.out.println("Model3Car.startEngine");
}
@Override
public void offEngine() {
System.out.println("Model3Car.offEngine");
}
@Override
public void pressAccelerator() {
System.out.println("Model3Car.pressAccelerator");
}
}package poly.car1;
public class K3Car implements Car {
@Override
public void startEngine() {
System.out.println("K3Car.startEngine");
}
@Override
public void offEngine() {
System.out.println("K3Car.offEngine");
}
@Override
public void pressAccelerator() {
System.out.println("K3Car.pressAccelerator");
}
}package poly.car1;
public class Driver {
private Car car;
public void setCar(Car car) {
System.out.println("자동차를 설정합니다: " + car);
this.car = car;
}
public void drive() {
System.out.println("자동차를 운전합니다.");
car.startEngine();
car.pressAccelerator();
car.offEngine();
}
}⭐ 클라이언트(Driver)에서 의존성을 인터페이스(Car)에 집중시켜서 코드 간소화 성공!
→ 클라이언트가 “구현’에 의존하지말고, “역할(인터페이스)”에 의존하게 해야 한다!
package poly.car1;
public class CarMain1 {
public static void main(String[] args) {
Driver driver = new Driver();
//차량 선택(k3)
K3Car k3Car = new K3Car();
driver.setCar(k3Car);
driver.drive();
//차량 변경(k3 -> model3)
Model3Car model3Car = new Model3Car();
driver.setCar(model3Car);
driver.drive();
}
}
Driver 클래스를 보면, 이제 K3Car, Model3Car를 알 필요 없이 Car라는 역할만 알고 있으면 된다.
동시에 새로운 자동차가 추가되어도 main 메서드에서만 코드가 변경될 뿐이지, Driver 클래스의 코드에서는 변경되는것이 아예 없어졌다.
🔥 우리는 Chapter 3에서 역할과 구현을 분리하여 클라이언트와 서버 관계를 충족하는 코드를 작성해보았다.
- Chapter 3 코드에서 새로운 차량(Model3Car)이 추가되어도
- 클라이언트(Driver)와 역할(Car)의 코드는 변경되지 않았다.
→ Closed for modification 충족- Model3Car라는 구현 대상을 생성 및 main 메서드에서 차량 설정 및 운전 코드를 생성했다.
→ Open for extension 충족
즉, 역할과 구현을 분리하여 클라이언트와 서버 관계를 충족시킨다면 OCP 원칙을 지키는 것이다!!
➕ 전략 패턴(Strategy Pattern)
디자인 패턴 중에 가장 중요한 패턴을 하나 뽑으라고 하면 전략 패턴을 뽑을 수 있다. 전략 패턴은 알고리즘을
클라이언트 코드의 변경 없이 쉽게 교체할 수 있다. 방금 설명한 코드가 바로 전략 패턴을 사용한 코드이다.
Car 인터페이스가 바로 전략을 정의하는 인터페이스가 되고, 각각의 차량이 전략의 구체적인 구현이 된다.
인터페이스를 통해 구현들을 클라이언트 코드( Driver )의 변경 없이 손쉽게 교체할 수 있다.
(문제 내용 : “김영한의 Java 기본 - 12. 다형성과 설계” PDF 참고)
PayService에서 KakaoPay, NaverPay를 의존하고 있다.
→ 결과적으로 클라이언트가 인터페이스를 의존하게 해야한다.
if-else 문에서 결제 방법 확인 및 결제작업(.pay())을 같이 하고 있다.
→ 코드 덩어리에 2개 이상의 기능이 섞여있다면, 분리해보는 시도를 해보자
혹시 모르니 중간마다 같은 결과를 출력하는지 실행해보도록 하자
→ 코드를 변경할 때 마다, 의도에 맞게 돌아가는지 항상 테스트 해봐야 한다.
➕ Null Object Pattern
→ 예외인 Null 값도 아예 하나의 경우로 미리 정해두어 코드에서 null을 다루지 않게 한다!
프로젝트를 진행하며 항상 Java의 기본기가 완벽하지 않다는 느낌을 받았습니다.
해당 강의를 통해 Java의 기본기를 다질 수 있었고, 주기적인 복습으로 추후 프로젝트에서 백엔드 Java 개발시에 설계 및 이슈 해결하는데 많은 도움이 될 것이라 생각합니다.
더 나은 백엔드 개발자 차재현이 되도록 노력하겠습니다!