ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인 패턴] 11. 템플릿 메서드 패턴 (Template Method Design Pattern)
    Computer Science/Design Pattern 2022. 11. 25. 02:45
    728x90

    학습목표 : 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
    반응형

    댓글

Keydi's Tistory