템플릿 메소드 패턴은 전체적으로 동일하면서 부분적으로는 다른 구문으로 구성된 메소드의 코드 중복을 최소화 할 때 유용하다.
다른관점에서 보면 동일한 기능을 상위 클래스에서 정의하면서 확장과 변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다
여러 회사의 모터 지원하기
엘리베이터 제어 시스템에서 모터를 구동시키는 기능을 생각해보자. 예를 들어 A모터를 이용하는 제어잇스템이라면 AMotor 클래스에 move()메소드를 정의할 수 있다.
또한 엘리베이터가 이미 이동중이라면 모터를 구동 시킬 필요가 없다. MortorStatus, DoorStatus, Direction은 Enumeration 인터페이스로 각각 모터의 상태, 문의 상태, 이동방향을 나타낸다.
public class CommonEnum {
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 AMotor {
private Door door;
private MotorStatus mortorStatus;
public AMotor(Door door) {
this.door = door;
mortorStatus = MotorStatus.STOPPED;
}
private void moveAMotor(Direction direction) {
}
public MotorStatus getMortorStatus() {
return mortorStatus;
}
public void setMortorStatus(MotorStatus mortorStatus) {
this.mortorStatus = mortorStatus;
}
public void move(Direction direction) {
MotorStatus motorStatus = getMortorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
DoorStatus doorStatus = door.getDoorStatus();
if (doorStatus == DoorStatus.OPENED) {
door.close();
}
moveAMotor(direction);
setMortorStatus(MotorStatus.MOVING);
}
}
public class Client {
public static void main(String[] args) {
Door door = new Door();
AMotor aMotor = new AMotor(door);
aMotor.move(Direction.UP);
}
}
AMotor클래스의 move()는 우선 getMotorStatus()를 호출해 모터의 상태를 조회한다. 모터가 이미 동작중이라면 move()실행을 종료한다. 그리고 Door클래스의 getDoorStatus()를 호출해 문의 상태를 조회한다. 문이 열려있는 상태면 Door클래스의 close()를 호출해 문을 닫는다. 그리고 moveAMotor()를 호출해 모터를 구동시킨다. 마지막으로는 setMotorStatus()를 호출해 모터의 상태를 MOVING상태로 바꾼다.
문제점
만약 B회사의 모터를 제어해야 한다면?
모터를 제어하는 내부 코드는 다를지언정 호출하는 메소드는 동일하다. 그러므로 코드가 중복되어 유지보수성을 악화시키므로 바람직하지 않다.
이런 코드 중복 문제는 다른 벤더가 들어올때마다 계속 발생한다.
이런 경우에는 상속을 이용해 코드 중복 문제를 피할 수 있다.
그러나 이 경우에도 여전히 모터를 움직이는 move()에서 부분적으로 중복코드가 발생한다.
해결책
move()처럼 완전히 중복되지는 않지만 부분적으로 중복되는 경우에도 상속을 통해 코드 중복을 피할 수 있다.
이런 경우 move()를 상위 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 void move(Direction direction) {
MotorStatus motorStatus = getMortorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
DoorStatus doorStatus = door.getDoorStatus();
if (doorStatus == DoorStatus.OPENED) {
door.close();
}
moveMotor(direction); ///
setMortorStatus(MotorStatus.MOVING);
}
protected void moveAMotor(Direction direction) {
}
}
public class AMotor extends Motor {
public AMotor(Door door) {
super(door);
}
protected void moveMotor(Direction direction) {
}
}
public class BMotor extends Motor {
public BMotor(Door door) {
super(door);
}
protected void moveMotor(Direction direction) {
}
}
두 벤더의 Motor클래스에서 대부분의 구문이 중복되었던 move()를 Motor로 이동했음을 확인할 수 있다. 즉, 다른부분만 벤더 클래스에서 구현했음을 확인할 수 있다.