엘리베이터 제어 시스템에서 모터를 구동시키는 기능을 생각해보자. 현대 모터를 사용하는 제어 시스템이라면 HyundaiMotor 클래스에 move 메서드를 정의할 수 있다.
HyundaiMotor 클래스는 move 메서드를 실행할 때 안전을 위해 문(Door 클래스)이 닫혀있는지 조사할 필요가 있다. 또한 엘리베이터가 이미 이동 중이면 모터를 작동시킬 필요가 없다.
각 모터의 상태 (정지중, 이동중), 문의 상태 (닫혀있는중, 열려있는중), 이동 방향(위로, 아래로)를 나타내는 Enumeration 인터페이스 설계다.
public enum DoorStatus{CLOSED, OPENED}
pubilc enum MotorStatus{MOVING, STOPPED}
public class Door{
pirvate 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=MotorStatus.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;
if(motorStatus==MotorSTatus.OPEND)//만약 문이 열려 있으면 우선 문을 닫음
door.close()
moveHyundaiMotor(direction);//모터를 주어진 방향으로 이동시킴
setMotorStatus(MotorStatus.MOVING)//모터를 상태 이동 중으로 변경
}
}
public class Client{
public static void mian(String[] args){
Door door = new Door();
HyundaiMotor hyundaiMotor=new HyundaiMotor(door);
hyundaiMotor.move(Direction.UP);
}
}
LG 모터를 구동하는 것은 현대 모터를 구동하는 것과 완전히 동일하지는 않다. 그리서 현대 모터를 구동시키는 HyundaiMotor 클래스를 복사한 후 LG 모터에 한정된 부분을 수정할 필요가 있다.
public class LGMotor{
private Door door;
private MotorStatus motorStatus;
public LGMotor(Door door){
this.door=door;
motorStatus=MotorStatus.STOPPED;
}
private void moveLGMotor(Direction direction){
return motorStatus;
}
private void setMotorStatus(MortorStatus motorStatus){
this.motorStatus=motorStatus;
}
public void move(Direction direction){
...
}
}
LGMotor와 HyundaiMotor를 비교해보면 여러 개의 메서드가 동일하게 구현되어 있음을 알 수 있다. 2개의 클래스는 많은 중복 코드를 가진다. 이런 중복 코드는 다른 회사의 모터를 사용할 때마다 발생한다.
2개 이상의 클래스가 유사한 기능을 제공하면서 중복된 코드가 있는 경우에 상속을 이용해서 문제를 피할 수 있다. HyundaiMotor와 LGMotor에서 공통의 상위 클래스로 Motor 클래스를 정의하고 여기에 중복 코드를 이동시킨다.
2개의 클래스에 있던 Door 클래스와의 연관 관계, motorStatus 필드, getMotorStatus와 setMotorStatus 메서드의 중복을 피할 수 있다. 하지만 move 메서드는 여전히 코드 중복 문제가 있다.
move 메서드처럼 완전히 중복되진 않지만 부분적으로 중복되는 경우에도 상속을 활용해 코드 중복을 피할 수 있다. move 메서드를 Motor 클래스로 이동시키고 다른 구문, 즉 moveHyundaiMotor와 moveLGMotor 메서드의 호출 부분을 하위 클래스에서 오버라이드하는 방식으로 중복을 피할 수 있다.
moveMotor 메서드의 구현이 서로 달라야 하므로 moveMotor 메서드는 Motor 클래스에서 추상 메서드로 정의한 후 하위 클래스에서 적절하게 오버라이드되도록 한다. 즉, HyundaiMotor 클래스는 moveMotor 메서드를 오버라이드하면서 현대 모터를 구동하는 코드를 작성하고 LGMotor 클래스의 moveMotor 메서드는 LG모터를 구동하는 코드를 작성한다.
public abstract class Motor{
private Door door;
private MotorStatus motorStatus;
...
//LGMotor와 HyundaiMotor의 move 메서드에서 공통되는 부분만을 가짐
public void move(Direction direction){
MotorStatus motorStatue=getMtorStatus();
if(motorStatus==MotorStatus.MOVING)
return;
if(doorStatus==DoorStatus.OPENDED)
door.close();
//이 메서드는 HyundaiMotor와 LGMotor에서 오버라이드 됨
moveMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
protected abstract void moveMotor(Direction direction);
}
public class HyundaiMotor extends Motor{
public HyundaiMotor(Door door){
super(door);
}
protected void moveMotor(Direction direction){
//HyundaiMotor 구동
}
}
public class LGMotor extends Motor{
public LGMotor(Door door){
super(door);
}
protected void moveMotor(Direction direction){
//LGMotor 구동
}
}
move 메서드 안에 다른 부분인 moveMotor만 각각의 클래스에서 따로 구현했다.
템플릿 메서드 패턴은 전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할 때 유용하다. 다른 관점에서 보면 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분을 서브 클래스에서 구현한다. 전체적인 알고리즘 코드를 재사용하는데 유용하다.
Motor 클래스의 move 메서드를 템플릿 메서드라 부르고, move 메서드에서 호출되면서 하위 클래스에서 오버라이드되는 moveMotor 메서드를 primitive 또는 hook 메서드라고 부른다.
템플릿 메서드를 정의하는 클래스.
하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive 메서드 또는 hook 메서드로 정의하는 클래스다.
물려받은 primitive 메서드나 hook 메서드를 구현하는 클래스.
상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메서드나 hook 메서드를 오버라이드 하는 클래스다.