Design Pattern

[Design Pattern] 변하는 코드 분리(1) Template Method

유자맛바나나 2022. 11. 16. 20:39

❑ 패턴 소개

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