템플릿 메서드 패턴
템플릿 메서드 패턴
- 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 기능을 수행하는 구조는 바꾸지 않으면서 특정 단계
에서 수행하는 내역을 바꾸는 패턴
- 전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할때 유용하다.
- 동일한 기능을 상위 클래스에서 정의하면서 확장 / 변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.
- 예를 들어, 전체적인 알고리즘은 상위 클래스에서 구현하면서 다른 부분은 하위 클래스에서 구현할 수 있도록 함으로
써 전체적인 알고리즘 코드를 재사용하는데 유용하도록 한다.
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 패턴을 이용하면 전체적으로 동일하면서 부분적으로는 다른 알고리즘으로 구성된
메서드의 코드 중복을 최소화할 수 있다.
- Abstract Class : Motor 클래스
- Concrete Class : HyundaiMotor, LGMotor
- Template Method : Motor 클래스의 move 메서드
- primitive or hook Method : move 메서드에서 호출되면서 하위 클래스에서 오버라이드될 필요가 있는
moveMotor 메서드