💡 정리
- 다형성이 가장 중요하다!
- 디자인 패턴 대부분은 다형성을 활용하는 것이다!
- 스프링의 핵심인 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 개발시에 설계 및 이슈 해결하는데 많은 도움이 될 것이라 생각합니다.
더 나은 백엔드 개발자 차재현이 되도록 노력하겠습니다!