설계 패턴

템플릿 메서드 패턴

연결리스트 2020. 11. 12. 22:56

템플릿 메서드 패턴 

 - 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 기능을 수행하는 구조는 바꾸지 않으면서 특정 단계

   에서 수행하는 내역을 바꾸는 패턴 

 - 전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할때 유용하다.

 - 동일한 기능을 상위 클래스에서 정의하면서 확장 / 변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.

 - 예를 들어, 전체적인 알고리즘은 상위 클래스에서 구현하면서 다른 부분은 하위 클래스에서 구현할 수 있도록 함으로

   써 전체적인 알고리즘 코드를 재사용하는데 유용하도록 한다. 

 

AbstractClass 

 - 템플릿 메서드를 정의하는 클래스 

 - 하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive, hook 메서드로 정의하는 클래스 

 

ConcreteClass

 - 물려받은 primitive, hook 메서드를 구현하는 클래스 

 - 상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive, hook 메서드를

   오버라이드하는 클래스

 

 

 

예시 

엘리베이터에 여러 회사의 모터 적용하기 

엘리베이터 제어 시스템에서 모터를 구동시키는 기능 

 - 현대 모터를 이용하는 엘리베이터라면 HyundaiMotor 클래스에 move 메서드를 정의할 수 있다. - HyundaiMotor 클래스  -->  Door 클래스    - move 메서드를 실행할 때 안전을 위해 Door가 닫혀있는지를 확인하기 위해 연관 관계를 정의 해준다.  

 - Enumeration Interface 

    - 모터의 상태 (MOVING, STOPPED)

    - 문의 상태 (OPENED, CLOSED)

    - 이동 방향 (UP, DOWN) 

 

Enum Interface

1
2
3
4
5
6
public enum DoorStatus { CLOSED, OPENED }
public enum MotorStatus { MOVING, STOPPED }
public enum Direction { UP, DOWN }

 

Door 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Door {
    private DoorStatus doorStatus;
    
    // doorStatus 생성자 기본 값으로 closed 상태를 가짐
    public Door(){
        doorStatus = DoorStatus.CLOSED;
    }
    public DoorStatus getDoorStatus(){
        return doorStatus;
    }
    public void open(){
        doorStatus = DoorStatus.OPENED;
    }
    public void close(){
        doorStatus = DoorStatus.CLOSED;
    }
}
cs

 

HyundaiMotor 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HyundaiMotor {
  private Door door;
  private MotorStatus motorStatus;
 
  public HyundaiMotor() {
    this.door = door;
    motorStatus = MotorStatus.STOPPED; // 초기: 멈춘 상태
  }
  private void moveHyundaiMotor(Direction direction) {
    // Hyundai Motor를 구동시킴
  }
  public MotorStatus getMotorStatus() { return motorStatus; }
  private void setMotorStatus() { this.motorStatus = motorStatus; }
 
  /* 엘리베이터 제어 */
  public void move(Direction direction) {
    MotorStatus motorStatus = getMotorStatus();
    // 이미 이동 중이면 아무 작업을 하지 않음
    if (motorStatus == MotorStatus.MOVING) return;
 
    DoorStatus doorStatus = door.getDoorStatus();
    // 만약 문이 열려 있으면 우선 문을 닫음
    if (doorStatus == DoorStatus.OPENED) door.close();
 
    // Hyundai 모터를 주어진 방향으로 이동시킴
    moveHyundaiMotor(direction);
    // 모터 상태를 이동 중으로 변경함
    setMotorStatus(MotorStatus.MOVING);
  }
}
 
cs

 

Main 클래스

1
2
3
4
5
6
7
8
public class Client {
  public static void main(String[] args) {
    Door door = new Door();
    HyundaiMotor hyundaiMotor = new HyundaiMotor(door);
    hyundaiMotor.move(Direction.UP); // 위로 올라가도록 엘리베이터 제어
  }
}
 
cs

 - HyundaiMotor 클래스의 move 메서드는 우선 getMotorStatus 메서드를 호출해 모터의 상태를 조회한다.

    - 모터의 상태가 동작 중이면 move 메서드의 실행을 종료

 - Door 클래스의 getDoorStatus 메서드를 호출해 문의 상태를 조회한다. 

     - 문이 열려있는 상태면 Door 클래스의 close 메서드를 호출해 문을 닫음 

     - 그리고 moveHyundaiMotor 메서드를 호출해 모터를 구동시킨다

     - setMotorStatus를 호출해 모터의 상태를 MOVING으로 기록한다. 

 

 

 

문제점

 - 다른 회사의 모터를 제어해야 하는 경우 OCP를 위반한다. 

 - 각 회사마다 모터를 제어하는 코드를 작성한 경우 모터를 제어하는 각 클래스들은 많은 중복 코드를 갖게된다. 

   중복 코드는 유지보수성을 악화시키므로 바람직한 구조가 아니다. 

 

 - 2개 이상의 클래스가 유사한 기능을 제공하면서 중복된 코드가 있는 경우 상속을 이용해 코드 중복을 해결할 수 있다.

 - 중복되는 기능을 Motor 클래스에 정의한 뒤 상속을 통해 해결할 수 있다.

 - Door 클래스와의 연관 관계, motorStatus, setMotorStatus, getMotorStatus 메서드의 중복이 해결가능하다. 

 - 하지만 HyundaiMotor와 LGMotor 클래스의 move 메서드는 여전히 대부분 비슷하다는 문제가 있다. 즉, move 메서드

   여전히 코드 중복 문제가 있는 것이다. 

 - move 메서드에서 각 회사의 모터를 호출하는 부분을 제외하고 모든 부분이 동일하고, 기능면에서도 동일하기 

   때문에 move 메서드를 상위 클래스인 Motor 클래스로 이동시키고 각 회사의 모터를 호출하는 부분을 하위 클래스

   에서 오버라이드하는 방식으로 해결할 수 있다.

 

 

Template Method 예시 

Motor 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* HyundaiMotor와 LGMotor의 공통적인 기능을 구현하는 클래스 */
public abstract class Motor {
 
    protected Door door;
    private MotorStatus motorStatus; // 공통2 motorStatus 필드
 
    // 공통1 Door 클래스와의 연관 관계
    public Motor(Door door){
        this.door = door;
        motorStatus = MotorStatus.STOPPED;
    }
 
    // 공통3 getMotorStatus, setMotorStatus 메서드
    protected MotorStatus getMotorStatus() {
        return motorStatus;
    }
    protected void setMotorStatus(MotorStatus motorStatus) {
        this.motorStatus = motorStatus;
    }
 
    // 템플릿 메서드
    public void move(Direction direction){
 
        MotorStatus motorStatus = getMotorStatus();
 
        // 이미 이동 중이면 아무 작업도 하지 않음 
        if(motorStatus == MotorStatus.MOVING)
            return;
 
        DoorStatus doorStatus = door.getDoorStatus();
 
        // 만약 문이 열려있으면 우선 문을 닫음
        if(doorStatus == DoorStatus.OPENED)
            door.close();
 
        // 모터를 주어진 방향으로 이동시킴 
        moveMotor(direction);
        // 모터 상태를 이동중으로 변경함
        setMotorStatus(MotorStatus.MOVING);
    }
 
    // 중복된 기능을 자식 클래스에서 사용할 수 있도록 추상화
    protected abstract void moveMotor(Direction direction);
}
 
cs

 

HyundaiMotor 클래스

1
2
3
4
5
6
7
8
9
10
11
/* Motor를 상속받아 HyundaiMotor 클래스를 구현 */
public class HyundaiMotor extends Motor{
  public HyundaiMotor(Door door) { 
        super(door); 
  }
  protected void moveMotor(Direction direction) {
    // Hyundai Motor를 구동시킴
    System.out.println("HyundaiMotor Initiated");
  }
}
 
cs

 

LGMotor 클래스

1
2
3
4
5
6
7
8
9
10
11
/* Motor를 상속받아 LGMotor 클래스를 구현 */
public class LGMotor extends Motor{
  public LGMotor(Door door) { 
    super(door); 
  }
  protected void moveMotor(Direction direction) {
    // LG Motor를 구동시킴
    System.out.println("LGMotor Initiated");
  }
}
 
cs

 - Motor 클래스에서 move 메서드는 HyundaiMotor와 LGMotor에서 동일한 기능을 구현하면서 각 하위 

   클래스에서 구체적으로 정의할 필요가 있는 moveMotor 메서드 부분만 각 하위 클래스에서 오버라이드 

   되도록 한다.

 - 이와 같이 Template Method 패턴을 이용하면 전체적으로 동일하면서 부분적으로는 다른 알고리즘으로 구성된 

   메서드의 코드 중복을 최소화할 수 있다.

 

Template Method

 - Abstract Class : Motor 클래스 

 - Concrete Class : HyundaiMotor, LGMotor 

 - Template Method : Motor 클래스의 move 메서드 

 - primitive or hook Method : move 메서드에서 호출되면서 하위 클래스에서 오버라이드될 필요가 있는

                                        moveMotor 메서드