Priv's Blog
11. 템플릿 메서드 패턴 본문
1. 여러 회사의 모터 지원하기
엘리베이터 제어 시스템에서 모터를 구동시킨다고 가정하자.
만약 엘리베이터가 현대 모터를 사용한다면, HyundaiMotor 클래스에 move 메서드를 정의할 수 있을 것이다.
move 메서드를 실행할 때는 문이 제대로 닫혔는지를 검사해야 한다.
즉, HyundaiMotor 클래스는 Door 클래스와 연관 관계가 있다.
또한 모터는 엘리베이터가 이동 중이 아닐 때만 구동되어야 한다.
MotorStatus, DoorStatus, Direction은 Enumeration 인터페이스로, 각각 모터의 상태와 문의 상태, 이동 방향을 나타낸다.
package P11;
enum DoorStatus {
CLOSED,
OPEND
}
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.OPEND;
}
}
package P11;
public class HyundaiMotor {
private Door door;
private MotorStatus motorStatus;
public HyundaiMotor(Door door) {
this.door = door;
motorStatus = MotorStatus.STOPPED;
}
private void moveHyundaiMotor(Direction direction) {
System.out.println("Motor Move");
}
public MotorStatus getMotorStatus() {
return motorStatus;
}
private void setMotorStatus(MotorStatus motorStatus) {
this.motorStatus = motorStatus;
}
public void move(Direction direction) {
MotorStatus motorStatus = getMotorStatus();
DoorStatus doorStatus = door.getDoorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
if (doorStatus == DoorStatus.OPEND) {
door.close();
}
moveHyundaiMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
}
2. 문제점
만약 다른 회사의 모터를 제어해야 한다면?
2.1) 다른 회사의 모터를 제어해야 하는 경우
LG 모터와 현대 모터는 동일하지 않을 것이다.
만약 LG 모터를 구동해야 한다면 어떻게 해야 할까?
package P11;
public class LGMotor {
private Door door;
private MotorStatus motorStatus;
public LGMotor(Door door) {
this.door = door;
motorStatus = MotorStatus.STOPPED;
}
private void moveLGMotor(Direction direction) {
System.out.println("Motor Move");
}
public MotorStatus getMotorStatus() {
return motorStatus;
}
private void setMotorStatus(MotorStatus motorStatus) {
this.motorStatus = motorStatus;
}
public void move(Direction direction) {
MotorStatus motorStatus = getMotorStatus();
DoorStatus doorStatus = door.getDoorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
if (doorStatus == DoorStatus.OPEND) {
door.close();
}
moveLGMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
}
위의 LGMotor 클래스의 내용을 살펴보면 사실상 HyundaiMotor 클래스의 내용과 동일하다는 것을 알 수 있다.
이러한 코드 중복은 유지 보수에 취약하므로 권장되지 않는 방식이다.
이처럼 2개 이상의 클래스가 유사한 기능을 제공하면서 중복된 코드가 있을 경우에는 상속을 사용해 문제를 해결할 수 있다.
package P11;
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;
}
}
package P11;
public class HyundaiMotor extends Motor {
public HyundaiMotor(Door door) {
super(door);
}
private void moveHyundaiMotor(Direction direction) {
System.out.println("Motor Move");
}
public void move(Direction direction) {
MotorStatus motorStatus = getMotorStatus();
DoorStatus doorStatus = door.getDoorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
if (doorStatus == DoorStatus.OPEND) {
door.close();
}
moveHyundaiMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
}
package P11;
public class LGMotor extends Motor {
public LGMotor(Door door) {
super(door);
}
private void moveLGMotor(Direction direction) {
System.out.println("Motor Move");
}
public void move(Direction direction) {
MotorStatus motorStatus = getMotorStatus();
DoorStatus doorStatus = door.getDoorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
if (doorStatus == DoorStatus.OPEND) {
door.close();
}
moveLGMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
}
이제 중복되었던 Door 클래스와의 관계, motorStatus 필드, getMotorStatus, setMotorStatus 메서드 등의 부분을 제거할 수 있게 되었다.
하지만 여전히 move 메서드를 보면 두 클래스가 매우 비슷하다.
즉, 여전히 코드 중복 문제가 완전히 해결된 것이 아니다.
3. 해결책
이 경우에도 상속을 사용하면 해결이 가능하다.
즉, move 메서드에서 메서드 호출 구문을 제외한 나머지와 모터 구동을 실제로 구현한다는 기능 면에서는 동일하다.
이 경우 move 메서드를 상위 Motor 클래스로 이동시키고 다른 구문(moveHyundaiMotor, moveLGMotor 메서드 호출 부분)을 하위 클래스에서 오버라이드 하는 방식으로 해결하면 된다.
package P11;
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();
DoorStatus doorStatus = door.getDoorStatus();
if (motorStatus == MotorStatus.MOVING) {
return;
}
if (doorStatus == DoorStatus.OPEND) {
door.close();
}
moveMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
protected abstract void moveMotor(Direction direction);
}
package P11;
public class HyundaiMotor extends Motor {
public HyundaiMotor(Door door) {
super(door);
}
protected void moveMotor(Direction direction) {
System.out.println("Motor Move");
}
}
package P11;
public class LGMotor extends Motor {
public LGMotor(Door door) {
super(door);
}
protected void moveMotor(Direction direction) {
System.out.println("Motor Move");
}
}
4. 템플릿 메서드 패턴
템플릿 메서드 패턴은 전체적으로 동일하면서 부분적으로는 다른 구문의 메서드 코드 중복을 최소화하는 패턴이다.
즉, 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현하는 것이다.
서로 다른(변화가 필요한) 부분은 각 하위 클래스에서 오버라이드 하도록 만들고, 나머지는 상위 클래스를 상속받아 사용한다.
위의 엘리베이터 모터 예시의 경우, Motor 클래스의 move 메서드는 템플릿 메서드, move 메서드에서 호출되어 하위 클래스에서 오버라이드 되어야 하는 moveMotor 메서드는 primitive 또는 hook 메서드라고 부른다.
템플릿 메서드는 전체적인 알고리즘을 상위 클래스에서 구현하며, 다른 부분은 하위 클래스에서 구현하도록 만드는 디자인 패턴이다.
이 덕분에 전체적인 알고리즘 코드를 재활용하는 데 유용하다.
템플릿 메서드 패턴을 모터 예제에 적용하면 다음과 같다.
수고하셨습니다!
'Dev. Study Note > Design Pattern' 카테고리의 다른 글
13. 추상 팩토리 패턴 (0) | 2022.07.03 |
---|---|
12. 팩토리 메서드 패턴 (0) | 2022.07.03 |
10. 데커레이터 패턴 (0) | 2022.07.03 |
9. 옵서버 패턴 (0) | 2022.07.03 |
8. 커맨드 패턴 (0) | 2022.07.03 |