해당 포스트는 "자바 객체지향 디자인 패턴", "JAVA 언어로 배우는 디자인 패턴 입문" 책의 내용을 요약한 것이다.



※ 템플릿 메서드 패턴(Template Method Pattern)

어떠한 요구사항에 대한 알고리즘이 있을 때(어떤 기능에 대한 순서가 있을 때) 알고리즘의 구조(기능을 실행시키는 순서)를 상위 추상 클래스의 메서드에 정의하고, 하위 클래스에서 알고리즘(기능에 대한 순서)의 변경 없이 알고리즘을 재정의하는 즉, 하위 클래스에서 알고리즘을 구체화하는 패턴이다.

 

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

 

public enum DoorStatus {CLOSED, OPENED}
public enum MotorStatus{ MOVING, STOPPED }

public class Door{
   private DoorStatus doorStatus;
 
   public Door(){
         doorStatus = DoorStatus.CLOSED;
   }
   
   public DoorStatus getDoorStatus(){
         return doorStatus;
   }

   public void close() {
        doorStatus = DoorStatus.CLOSED;
    }
   
   public void open() {
       doorStatus = DoorStatus.OPENED;
   }
}
public class HyundaiMotor{
   private Door door;
   private MotorStatus motorStatus;
  
   public HyundaiMotor(Door door){
       this.door = door;
       motorStatus = MototStatus.STOPPED;   //초기에는 멈춘 상태
   }
   
   private void moveHyundaiMotor(Direction direction){
        //Hyundai Motor를 구동시킴
   }
   
    public MotorStatus getMotorStatus(){
        return motorStatus;
    }
    
    private 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();
        moveHyundaiMotor(direction);    //모터를 주어진 방향으로 이동시킴
        setMotorStatus(MotorStatus.MOVING);
    }
}
public class Client{
    public static void main(String[] args){
         Door door = new Door();
         HyundaiMotor hyundaiMotor = new HyundaiMotor(door);
         hyundaiMotor.move(Direction.UP);
   }
}

 

- 문제점

HyundaiMotor 클래스는 현대 모터를 구동시킨다. 만약 LG 모터를 구동시키려면 현대 모터를 구동시키는 HyundaiMotor 클래스를 복사한 후 LG 모터에 한정된 부분을 수정할 필요가 있다.

 

public class LGMotor{
   private Door door;
   private MotorStatus motorStatus;
  
   public LGMotor(Door door){
       this.door = door;
       motorStatus = MototStatus.STOPPED;   //초기에는 멈춘 상태
   }
   
   private void moveLGMotor(Direction direction){
        //LG Motor를 구동시킴
   }
   
    public MotorStatus getMotorStatus(){
        return motorStatus;
    }
    
    private 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();
        moveLGMotor(direction);    //모터를 주어진 방향으로 이동시킴
        setMotorStatus(MotorStatus.MOVING);
    }
}

LGMotor 클래스와 HyundaiMotor 클래스를 비교해보면 여러 개의 메서드가 동일하게 구현되어 있다. 이렇게 코드가 중복되있으면 유지보수성을 악화시키므로 바람직하지 않다. 이러한 코드 중복문제는 Samsung Motor, OtisMotor 클래스를 만들 경우에도 마찬가지 문제가 된다. 그래서 보드 중복을 피하기 위해 공통의 상위 클래스로 Motor 클래스를 정의한다.

 

public abstract class Motor{
   protected Door door;
   private MotorStatus motorStatus;
  
   public Motor(Door door){
      this.door = door;
      motorStatus = MotorStatus.STOPPED;
   }
   public MotorStatus getMotorStatus() {
      return motorStatus;
   }
   protected void setMotorStatus(MotorStatus motorStatus) {
       this.motorStatus = motorStatus;
   }
}

public class HyundaiMotor extends Motor{ 
   public HyundaiMotor(Door door){
      super(door);
   }
   
   private void moveHyundaiMotor(Direction direction){
     //HyundaiMotor를 구동시킴
   }

   public void move(Direction direction){
      MotorStatus motorStatus = getMotorStatus();
      if(motorStatus == MotorStatus.MOVING)
           return;
      DoorStatus doorStatus = door.getDoorStatus();
      if(doorStatus == DoorStatus.OPENED)
          door.close();
      moveHyundaiMotor(Direction);
      setMotorStatus(MotorStatus.MOVING);
    }
}

public class LGMotor extends Motor{ 
   public LGMotor(Door door){
      super(door);
   }
   
   private void moveLGMotor(Direction direction){
     //LGMotor를 구동시킴
   }

   public void move(Direction direction){
      MotorStatus motorStatus = getMotorStatus();
      if(motorStatus == MotorStatus.MOVING)
           return;
      DoorStatus doorStatus = door.getDoorStatus();
      if(doorStatus == DoorStatus.OPENED)
          door.close();
      moveLGMotor(Direction);
      setMotorStatus(MotorStatus.MOVING);
    }
}

Motor 클래스를 정의함으로써 HyundaiMotor 클래스와 LGMotor 클래스에서의 많은 코드 중복 문제를 해결했다. 하지만 move 메서드를 보면 moveLG(Hyundai)Motor(Direction); 코드를 제외하고 코드가 중복되는 문제가 있다. move 메서드는 전체적으로는 동일하지만 부분적으로는 다른 구문으로 되어 있다. 이 때 템플릿 메서드 패턴을 사용하면 코드 중복을 최소화할 수 있다.

public abstract class Motor{
   protected Door door;
   private MotorStatus motorStatus;
  
   public Motor(Door door){
      this.door = door;
      motorStatus = MotorStatus.STOPPED;
   }
   public 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);
}

public class HyundaiMotor extends Motor{ 
   public HyundaiMotor(Door door){
      super(door);
   }
   
   private void moveMotor(Direction direction){
     //HyundaiMotor를 구동시킴
   }
}

public class LGMotor extends Motor{ 
   public LGMotor(Door door){
      super(door);
   }
   
   private void moveMotor(Direction direction){
     //LGMotor를 구동시킴
   }
}

위 코드는 템플릿 메서드 패턴을 적용한 예이다. 보면 move 메서드에서 각 클래스마다 다른 부분을 moveMotor 추상 메서드로 만들어 하위 클래스에서 moveMotor 메서드를 구체화하도록 했다. 여기서 move 메서드를 템플릿 메서드라고 부르고, move 메서드에서 호출되면서 하위 클래스에서 오버라이드될 필요가 있는 moveMotor 메서드는 primitive 도는 hook 메서드라고 부른다. 템플릿 메서드 패턴을 통해서 로직을 공통화 할 수 있고, 상위 클래스의 템플릿 메서드에서 알고리즘이 기술되어 있으므로, 하위 클래스에서는 알고리즘을 일일이 기술할 필요가 없다.

'자바 > 디자인패턴' 카테고리의 다른 글

추상 팩토리 패턴  (0) 2017.07.05
팩토리 메서드 패턴  (0) 2017.07.04
중재자(Mediator) 패턴  (0) 2017.07.04
커맨드(Command) 패턴  (0) 2017.07.03
상태(State) 패턴  (0) 2017.07.03

+ Recent posts