❑ 패턴 소개
1) GoF 패턴 분류
행위 패턴(Behavioral Pattern)
[참고] GoF's Patterns
더보기
생성 패턴 | 구조 패턴 | 행위 패턴 |
- 객체를 생성하는 것과 관련된 패턴 - 객체의 생성과 변경이 전체 시스템에 미치는 영향을 최소화하고 코드의 유연성을 높임 |
- 큰 구조를 형성하기 위해 클래스, 객체들을 어떻게 구성하고 합성할지 정하는데 활용할수 있는 패턴화한 것 - 복잡한 구조의 개발과 유지보수를 쉽게 만듦 |
- 반복적으로 사용되는 객체들의 상호작용을 패턴화한 것 |
❑ Factory Method ❑ Abstract Factory ❑ Builder ❑ Prototype ❑ Singleton |
❑ Adapter ❑ Bridge ❑ Composite ❑ Decorator ❑ Facade ❑ Flyweight ❑ Proxy |
❑ Chain of Responsibility ❑ Command ❑ Iterator ❑ Interpreter ❑ Mediator ❑ Memento ❑ Observer ❑ State ❑ Strategy ❑ Template Method ❑ Visitor |
2) 패턴의 의도
- 로직에서 변하는 코드를 찾아 상속을 이용한 '추상 메서드'로 분리한다. 변하는 코드를 찾아 분리 하는 것이 핵심이다.
- 변하는 코드를 분리하지 않은 채 개발한다면 새로운 조건이 추가될 때 마다 기존의 코드를 수정해야하기 때문에 OCP(Open-Closed Pricipal)를 위배하게 된다.
3) 유사한 패턴
3-1) Strategy Pattern(전략 패턴)
- '로직에서 변하는 코드를 찾아 분리한다'는 점에서 전략 패턴과 기본적으로 의도가 동일하다.
- 하지만 변하는 코드를 상속을 이용해 '추상 메서드'로 분리하는 템플릿 메서드와 달리, 전략 패턴은 변하는 코드를 '다른 클래스'로 분리 한다는 점에서 차이가 있다
3-2) Factory Method Pattern(팩토리 메서드 패턴)
❑ 예제
- 자동차의 동력 장치(PowerUnit)에 시동을 거는 기능 startEngine()을 만들고자 한다.
- 자동차의 동력 장치는 가솔린 엔진이 될 수도 있고, 디젤 엔진이 될 수도 있다. 따라서 가솔린과 디젤 별로 시동될 수 있는 조건을 만족하는지 확인하는 로직(checkConditionForStart)이 필요하다.
[Bad Design] 조건문 이용
1) 구현
- Bad Case 예제는 전략 패턴의 것과 완전히 동일하다
- "가솔린 엔진이냐, 디젤 엔진이냐"를 구분할 때 가장 직관적이고 쉽게 떠올리는 방법은 if, else 문이다.
- 조건문을 이용해 코드를 구현해보고 무엇이 문제인지 파악해본다
PowerUnitType.java
public enum PowerUnitType {
GASOLINE, DIESEL
}
- 동력 장치는 가솔린과 디젤 두 가지가 있다. enum을 이용해 타입을 구분해준다.
PowerUnit.java
public class PowerUnit {
protected PowerUnitType powerType;
private static final String ENGINE_ON = "ON";
private static final String ENGINE_OFF = "OFF";
private String engineStatus = ENGINE_OFF;
public PowerUnit(PowerUnitType powerType) {
this.powerType = powerType;
}
public void startEngine(PowerUnitType powerType) {
if (checkConditionForStart(powerType)) {
engineStatus = ENGINE_ON;
}
}
public boolean checkConditionForStart(PowerUnitType powerType) {
boolean result = false;
if (powerType == PowerUnitType.GASOLINE) {
System.out.println("Check condition for stating GASOLINE Engine");
// ..
result = true;
} else if (powerType == PowerUnitType.DIESEL) {
System.out.println("Check condition for stating DIESEL Engine");
// ..
result = true;
}
return result;
}
}
- 살펴볼 부분은 checkConditionForStart() 이다. if, else if 를 이용해 powerType을 구분하고, 각 powerType 별 엔진 시동을 위한 컨디션을 체크하게 된다.
2) 요구사항의 변경: 전기차가 추가된다면?
- 조건문을 이용해 구현했을 때 요구사항이 변경되어 전기차가 추가될 경우 문제점이 무엇인지 파악해본다
PowerUnit.java
public class PowerUnit {
protected PowerUnitType powerType;
private static final String ENGINE_ON = "ON";
private static final String ENGINE_OFF = "OFF";
private String engineStatus = ENGINE_OFF;
public PowerUnit(PowerUnitType powerType) {
this.powerType = powerType;
}
public void startEngine(PowerUnitType powerType) {
if (checkConditionForStart(powerType)) {
engineStatus = ENGINE_ON;
}
}
public boolean checkConditionForStart(PowerUnitType engineType) {
boolean result = false;
if (engineType == PowerUnitType.GASOLINE) {
System.out.println("Check condition for stating GASOLINE Engine");
// ..
result = true;
} else if (engineType == PowerUnitType.DIESEL) {
System.out.println("Check condition for stating DIESEL Engine");
// ..
result = true;
} else if (engineType == PowerUnitType.ELECTRIC) { // 전기차 조건 추가
System.out.println("Check condition for stating DIESEL Engine");
// ..
result = true;
}
return result;
}
}
- checkConditionForStart()에서 else if(engineType == PowerUnitType.) { } 추가해 전기차 로직을 처리해야 한다.
- 즉, 기존 클래스인 PowerUnit 를 수정한 것이다. 기존 클래스가 수정된 것은 "기능 추가에는 열려있고, 수정에 대해서는 닫혀 있어야 한다"는 OCP를 위반한 것이다.
그럼 어떻게 작성하는게 좋은 디자인일까?
변하는 코드를 분리하자!
어디로? 상속을 활용해 서브 클래스로!
[Good Pattern] 상속을 활용해 '추상 메서드'로 분리(Template Method)
1) 구현
- PowerUnit을 abstract 클래스로 변경하고 GasolineEngine, DieselEngine 클래스를 만들어 PowerUnit을 상속한다
- 변하는 코드인 checkConditionForStart() 메서드를 추상 메서드로 분리할 수 있도록 abstract 메서드로 변경한다
PowerUnit.java
// abstract 클래스로 변경
public abstract class PowerUnit {
protected PowerUnitType powerType;
private static final String ENGINE_ON = "ON";
private static final String ENGINE_OFF = "OFF";
private String engineStatus = ENGINE_OFF;
public void startEngine(PowerUnitType powerType) {
if (checkConditionForStart(powerType)) {
engineStatus = ENGINE_ON;
}
}
// abstract 메서드로 변경
public abstract boolean checkConditionForStart(PowerUnitType engineType);
}
GasolineEngine.java (추가)
public class GasolineEngine extends PowerUnit {
private PowerUnitType powerType = PowerUnitType.GASOLINE;
@Override
public boolean checkConditionForStart(PowerUnitType engineType) {
boolean result = false;
if (engineType == PowerUnitType.GASOLINE) {
System.out.println("Check condition for stating GASOLINE Engine");
// ..
result = true;
}
return result;
}
}
DieselEngine.java (추가)
public class DieselEngine extends PowerUnit {
private PowerUnitType powerType = PowerUnitType.DIESEL;
@Override
public boolean checkConditionForStart(PowerUnitType engineType) {
boolean result = false;
if (engineType == PowerUnitType.GASOLINE) {
System.out.println("Check condition for stating GASOLINE Engine");
// ..
result = true;
}
return result;
}
}
2) 요구사항의 변경: 전기차가 추가된다면?
- 템플릿 메서드 패턴을 활용해 구현했을 때 요구사항이 변경되어 전기차가 추가될 경우 장점이 무엇인지 파악해본다
ElectricMotor.java (추가)
public class ElectricMotor extends PowerUnit {
private PowerUnitType powerType = PowerUnitType.ELECTRIC;
@Override
public boolean checkConditionForStart(PowerUnitType engineType) {
boolean result = false;
if (engineType == PowerUnitType.ELECTRIC) {
System.out.println("Check condition for stating ELECTRIC Motor");
// ..
result = true;
}
return result;
}
}
- 전기차 조건을 체크하기 위한 새로운 클래스 ElectricMotor가 추가되었다. 기존 코드에는 아무런 변경사항도 없고, 새로운 기능이 추가되었으므로 OCP를 만족한다.
[Class Diagram]
Standard | Example |
'Design Pattern' 카테고리의 다른 글
[Design Pattern] 변하는 코드 분리(2) Strategy Pattern (0) | 2022.11.21 |
---|---|
[Design Pattern] OOP Design Principle : 디자인 패턴을 관통하는 원칙 (0) | 2021.11.14 |