-
[디자인 패턴] 11. 템플릿 메서드 패턴 (Template Method Design Pattern)Computer Science/Design Pattern 2022. 11. 25. 02:45728x90
학습목표 : 1) 공통 코드의 재사용 방법 이해, 2) 템플릿 메서드 패턴을 이용한 코드 재사용 방법 이해
템플릿 메서드 패턴 (Template Method Pattern)
전체적으로 동일하면서 부분적으로 다른 문장을 가지는 메서드의 코드 중복을 최소화할 때 유용한 패턴이다.
동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.
즉, 전체적인 알고리즘 코드를 재사용하는 데 유용한 패턴이다.
- AbstractClass : 템플릿 메서드를 정의하는 클래스
- 하위 클래스에서 공통 알고리즘을 정의하고, 하위 클래스에서 구현될 기능을 primitive 또는 hook 메서드로 정의하는 클래스다.
- ConcreteClass : 물려받은 primitive 또는 hook 메서드를 구현하는 클래스
- 상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 또는 hook 메서드를 오버라이드 하는 클래스다.
템플릿 메소드 패턴 예시 - 엘리베이터 제어 시스템
엘리베이터 제어 시스템에서 모터를 구동시키는 기능
HyundaiMotor 클래스 : 모터를 제어해서 엘리베이터를 이동시키는 클래스
Door 클래스 : 문을 열거나 닫는 기능을 제공하는 클래스
문제점 - LG 모터를 추가할 경우
다른 회사의 모터를 제어할 경우, 해당 모터를 제어할 클래스가 추가로 필요하다.
// LG 모터 클래스 public class LGMotor { private Door door; // 문 private MotorStatus motorStatus; // 모터 상태 변수 // 생성자 public LGMotor(Door door) { this.door = door; this.motorStatus = MotorStatus.STOPPED; } // LG 모터 구동 private void moveLGMotor(Direction direction) { // LG 모터 구동 System.out.println("LG 모터 구동 방향 : " + direction); } public MotorStatus getMotorStatus() { return motorStatus; } public void setMotorStatus(MotorStatus motorStatus) { this.motorStatus = motorStatus; } // 이동 public void move(Direction direction) { MotorStatus motorStatus = getMotorStatus(); // 모터가 구동 중이면 메서드 종료 if (motorStatus == MotorStatus.MOVING) { return; } DoorStatus doorStatus = this.door.getDoorStatus(); // 문이 열려있으면 문을 닫음 if (doorStatus == DoorStatus.OPENED) { door.close(); } moveLGMotor(direction); // 모터를 주어진 방향으로 작동 setMotorStatus(MotorStatus.MOVING); // 모터 상태를 이동중으로 변경 } }
다른 회사의 모터를 추가하여 사용할 때마다 코드의 중복이 발생하는 문제가 있다.
공통 기능을 하는 Motor 클래스 분리
// HyundaiMotor, LGMotor 공통적인 기능을 구현하는 클래스 public abstract class Motor { protected Door door; // 문 private MotorStatus motorStatus; // 모터 상태 변수 // 생성자 public Motor(Door door) { this.door = door; this.motorStatus = MotorStatus.STOPPED; } public MotorStatus getMotorStatus() { return motorStatus; } public void setMotorStatus(MotorStatus motorStatus) { this.motorStatus = motorStatus; } }
// 모터 추상 클래스를 상속받은 현대 모터 클래스 public class HyundaiMotor extends Motor { public HyundaiMotor(Door door) { super(door); } // 현대 모터 구동 메서드 private void moveHyundaiMotor(Direction direction) { // 현대 모터 구동 System.out.println("현대 모터 구동 방향 : " + direction); } 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); } }
// 모터 추상 클래스를 상속받은 LG 모터 클래스 public class LGMotor extends Motor { public LGMotor(Door door) { super(door); } // LG 모터 구동 클래스 private void moveLGMotor(Direction direction) { // LG 모터 구동 System.out.println("LG 모터 구동 방향 " + direction); } 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); } }
// 클라이언트 public class Client { public static void main(String[] args) { Door door = new Door(); HyundaiMotor hyundaiMotor = new HyundaiMotor(door); hyundaiMotor.move(Direction.DOWN); LGMotor lgMotor = new LGMotor(door); lgMotor.move(Direction.UP); } }
코드의 중복 공통되는 클래스를 분리했음에도, move() 메서드에 코드의 중복이 여전히 발생한다.
HyundaiMotor와 LG Motor의 move() 메서드의 코드 흐름은 동일하다.
따라서, 해당 중복되는 절차를 템플릿으로서 상위로 분리할 필요가 있다.
해결책
// 모터 클래스 public abstract class Motor { private Door door; // 문 private MotorStatus motorStatus; // 모터 상태 변수 // 생성자 public Motor(Door door) { this.door = door; this.motorStatus = MotorStatus.STOPPED; } public MotorStatus getMotorStatus() { return motorStatus; } public 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); } // 현대 모터에 맞게 구동 메서드 오버라이드 @Override protected void moveMotor(Direction direction) { // 현대 모터 구동 System.out.println("현대 모터 구동 방향 : " + direction); } }
// LG 모터 클래스 : 모터 클래스 상속 public class LGMotor extends Motor { public LGMotor(Door door) { super(door); } // LG 모터에 맞게 구동 메서드 오버라이드 @Override protected void moveMotor(Direction direction) { // LG 모터 구동 System.out.println("LG 모터 구동 방향 : " + direction); } }
// 클라이언트 public class Client { public static void main(String[] args) { Door door = new Door(); HyundaiMotor hyundaiMotor = new HyundaiMotor(door); hyundaiMotor.move(Direction.DOWN); LGMotor lgMotor = new LGMotor(door); lgMotor.move(Direction.UP); } }
최종적으로 템플릿 메소드 패턴을 적용하여 알고리즘의 코드 중복을 줄였다.
- Motor클래스는 AbstractClass역할을 수행
- HyundaiMotor클래스와 LGMotor클래스는 각각 ConcreteClass역할을 수행
- Motor클래스의 move()메서드는 템플릿 메서드, moveMotor()메서드는 primitive메서드에 해당
템플릿 메소드 패턴 실습
- 템플릿 메소드 패턴 : 알고리즘을 캡슐화
커피와 홍차 만들기
커피와 홍차 만들기의 공통점
- 카페인 음료
- 만드는 절차가 비슷하다 (알고리즘이 비슷하다) -> 템플릿 메서드 패턴 사용
클래스 다이어그램 - 커피와 홍차를 만드는 절차를 정의한 prepareRecipe() 메서드를 추상화한다.
- 커피와 홍차를 만드는 절차 중 동일한 부분은 상위 추상 클래스에서 구현
- 다른 부분은 하위 클래스에서 다르게 구현한다.
CaffeineBeverage
public abstract class CaffeineBeverage { //final 이용 자식클래스 오버라이드 금지 - (알고리즘 변경 금지) //-------------템플릿 메소드 패턴 부분------------------------------ final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } //------------------------------------------------------------------ //하위 클래스에서 다르게 구현 (세부 뭘 우려내는지, 뭘 추가하는지) abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("물 끓이는 중.."); } void pourInCup() { System.out.println("컵에 따르는 중.."); } }
Coffee
public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("커피를 우려내는 중.."); } public void addCondiments() { System.out.println("설탕과 우유를 추가하는 중.."); } }
Tea
public class Tea extends CaffeineBeverage { public void brew() { System.out.println("찻잎을 우려내는 중.."); } public void addCondiments() { System.out.println("레몬을 추가하는 중.."); } }
Main
public class TemplateMethodTest { public static void main(String[] args) { Tea myTea = new Tea(); myTea.prepareRecipe(); System.out.println("------------------------------"); Coffee myCoffee = new Coffee(); myCoffee.prepareRecipe(); } }
템플릿 메서드 패턴을 이용해 각 하위 클래스에서의 알고리즘 코드 중복이 줄였다.
실행 결과 728x90반응형'Computer Science > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 13. 추상 팩토리 패턴 (Abstract Factory Design Pattern) (0) 2022.12.20 [디자인 패턴] 12. 팩토리 메서드 패턴 (Factory Method Design Pattern) (0) 2022.12.20 [디자인 패턴] 10. 데커레이터 패턴 (Decorator Design Pattern) (0) 2022.11.11 [디자인 패턴] 9. 옵저버 패턴 (Observer Design Pattern) (0) 2022.11.10 [디자인패턴] 8. 커맨드 패턴 (Command Design Pattern) (0) 2022.10.31 - AbstractClass : 템플릿 메서드를 정의하는 클래스