Java/고급객체지향

고급객체지향 프로그래밍 - 템플릿 메소드 패턴(Template Method pattern)

얄루몬 2021. 12. 12. 15:13

1. template method pattern이란?

간단하게 구현된 알고리즘의 직접적인 변경 없이(알고리즘의 구조 변경 없이) 서브클래스를 사용하여 일부를 재정의하여 사용할 수 있는 것을 의미한다. 

 

<아래 음료를 만드는 클래스를 통해서 템플릿 메소드 패턴을 사용하는 경우를 살펴보자>

package smu_2;

public abstract class CaffeineBeverage{
	public final void prepareRecipe() {//final로 레시피(알고리즘)를 고정시켜 놓는다. 오버라이딩을 금지시키기 위함
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}
	
	/*추상메소드를 사용해서 자식클래스내에서 필요한대로 구현하게 하기 위함
	 왜냐면 커피, 홍차 등 음료마다 커피를 끓이거나 홍차를 끓이거나 넣는 
	 여러가지들이 다르기 때문에 추상클래스로 만들어서 미완성된 설계도를
	 제공하여 자손들이 설계완료 시키는 형식으로 진행한다.*/
	public abstract void brew();
	public abstract void addCondiments();	
	
	
	//공통으로 사용될 메소드는 그냥 구현해놓으면 된다. 
	public void boilWater() {
		System.out.println("물을 끓이는 중입니다.");	
	}
	public void pourInCup() {
		System.out.println("음료를 컵에 담는 중입니다.");
	}

}

 

2. 탬플릿 메소드 사용 시 이점

  • 중복코드가 줄어든다.
  • 유지보수가 용이해진다.

중복된 코드는 구현을 미리시켜두고 달라지는 부분은 상속받은 클래스에서 구현을 해주면 중복코드는 줄어들고 유지보수는 바뀌는 부분만 수정하면 되니 유지보수가 용이해지게 된다.

 

 

#오타 concretemethod임 

package smu_2;

abstract class AbstractClass{
//무조건 final을 사용하란 것은 아니지만 알고리즘의 고정을 위함. 
	final void templateMethod() {
		primitiveOperation1();
		primitiveOperation1();
		concreteOperation();
	}
	
	abstract void primitiveOperation1();
	abstract void primitiveOperation2();
	void concreteOperation() {
		//구현 --> 위임하지 않는 부분을 말한다.
	}
	
}

 

 

3. 다른 경우에 사용하는 템플릿 메소드 패턴(Feat. hook( ))

<hook의 일반적 예시>

package smu_2;

abstract class AbstractClass{
	final void templateMethod() {
		primitiveOperation1();
		primitiveOperation1();
		concreteOperation();
		hook();
	}
	
	abstract void primitiveOperation1();
	abstract void primitiveOperation2();
	void concreteOperation() {
		//구현 --> 위임하지 않는 부분을 말한다.
	}
	void hook() {} //기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드
	
}

 

<Hook은 사용법에 따라서 우리가 알아서 고쳐 쓰면 된다.>

package smu_2;

public abstract class CaffeineBeverageWithHook{
	public final void prepareRecipe() {//final로 레시피(알고리즘)를 고정시켜 놓는다. 오버라이딩을 금지시키기 위함
		boilWater();
		brew();
		pourInCup();
		if (customerWantsCondiments()) {
			//사용자가 뭘 더 넣는다 하면 addCondiments를 한다.
			addCondiments();
		}
	}
	
	

	public abstract void brew();
	public abstract void addCondiments();	
	
	public void boilWater() {
		System.out.println("물을 끓이는 중입니다.");	
	}
	public void pourInCup() {
		System.out.println("음료를 컵에 담는 중입니다.");
	}
	boolean customerWantsCondiments() {
		//Hook
		return true;
	}

}

 

<hook을 통해 서브클래스에서 알고리즘의 수정변경 없이 템플릿 메소드 패턴을 사용한 코드>

package smu_2;

import java.util.Scanner;

public abstract class CaffeineBeverageWithHook{
	public final void prepareRecipe() {//final로 레시피(알고리즘)를 고정시켜 놓는다. 오버라이딩을 금지시키기 위함
		boilWater();
		brew();
		pourInCup();
		if (customerWantsCondiments()) {
			//사용자가 뭘 더 넣는다 하면 addCondiments를 한다.
			addCondiments();
		}
	}
	
	

	public abstract void brew();
	public abstract void addCondiments();	
	
	public void boilWater() {
		System.out.println("물을 끓이는 중입니다.");	
	}
	public void pourInCup() {
		System.out.println("음료를 컵에 담는 중입니다.");
	}
	boolean customerWantsCondiments() {
		//Hook
		return true;
	}

}
package smu_2;

import java.util.Scanner;

//상속
public class CoffeeWithHook extends CaffeineBeverageWithHook{
	public void brew() {
		System.out.println("필터로 커피를 우려내는 중");
	}
	public void addCondiments() {
		System.out.println("우유와 설탕을 추가하는 중");
	}
	public boolean customerWantsCondiments() {
		char answer = getUserInput();
		//입력받은 값이 y -> True no -> False
		return (answer == 'y') ? true:false;	
	}
	public char getUserInput() {
		String answer = null;
		System.out.println("음료에 우유와 설탕을 추가하시겠습니까?");
		Scanner sc = new Scanner(System.in);
		answer = sc.next();
		return answer.charAt(0);
	}
}
package smu_2;

public class BeverageTestDrive {

	public static void main(String[] args) {

		CoffeeWithHook coffeeHook = new CoffeeWithHook();
		System.out.print("음료 준비중...\n");
		coffeeHook.prepareRecipe();
		
	}

}

결과

음료 준비중...
물을 끓이는 중입니다.
필터로 커피를 우려내는 중
음료를 컵에 담는 중입니다.
y
우유와 설탕을 추가하는 중

 

음료 준비중...
물을 끓이는 중입니다.
필터로 커피를 우려내는 중
음료를 컵에 담는 중입니다.
n

 

 

4. 할리우드 원칙(Hollywood Principle)

  • 템플릿 메소드 패턴을 이용하여 설계를 하면 할리우드 원칙이 적용된다.

        -> 템플릿 메소드를 이용해서 프로그램을 설계하면서 "우리가 연락할 테니 먼저 연락하지 마" 라고 얘기하는 격

  •  저수준(low level) 구성 요소에서 시스템에 접속할 수 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정한다. (저수준 - 서브클래스/ 고수준 - 알고리즘)

 

5. 템플릿 메소드 패턴의 활용 예

  • 프레임워크를 만드는 데 아주 훌륭한 디자인 도구
  • 프레임워크를 사용함으로써 작업이 처리되는 방식은 제어하면서, 프레임워크에서 처리하는 알고리즘의 각 단계는 그 프레임워크를 사용하는 사람 마음대로 지정이 가능하다.
  • Array의 sort() 함수
  • 스윙의 JFrame
  • Applet

 

 


템플릿 메소드를 이용한 BubbleSort 구현 

 

package 고객프;

import java.util.Arrays;

public class MainTest {
	public static void main(String[] args) {
		Person[] people = {
			new Person(3000, "Dooly"),
			new Person(30, "Ddochi"),
			new Person(25, "Michol"),
			new Person(20000, "Douner"),
			new Person(3, "HeeDong")
		};
		System.out.println("\noriginal people");
		for (Person p : people) {
			System.out.println(p);
		}
		
		System.out.println("\nsort by Name");
		NameSorter sorter = new NameSorter();
		sorter.bubbleSort(people);
		for (Person p : people) {
			System.out.println(p);
		}
		
		System.out.println("\nsort by Age");
		AgeSorter sorter2 = new AgeSorter();
		sorter2.bubbleSort(people);
		for (Person p : people) {
			System.out.println(p);
		}
	}

}

 

package 고객프;

public class AgeSorter extends BubbleSorter{
	public void bubbleSort(Person[] data) {
        sort(data);
    }
	public int compareTo(Person a, Person b) {
		if (a.getAge() > b.getAge()) return 1;
		else if (a.getAge() < b.getAge()) return -1;
		else return 0;
	}

}

 

package 고객프;

public class NameSorter extends BubbleSorter{

	public void bubbleSort(Person[] data) {
		sort(data);
    }
	public int compareTo(Person a, Person b) {
		return a.getName().compareTo(b.getName());
	}
}

 

package 고객프;

public class Person implements Comparable<Person> {
	private int age;
	private String name;
	
	public Person(int age, String name) {
		this.age = age;
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [age=" + age + ", name=" + name + "]";
	}

	@Override
	public int compareTo(Person other) {
		return this.getAge() - other.getAge();
	}
}

 

 

<버블 정렬 알고리즘을 추사화 시키고, 그 중 이름과 나이를 비교하는 부분을 자식 클래스들로 위임하는 형태로 코드를 작성.> 

package 고객프;

public abstract class BubbleSorter implements BubbleSorterComparable{
	public void sort(Person[] data) {
        for (int i = 0; i < data.length - 1; i++) {
            for (int j = 0; j < data.length - 1; j++) {
                if (compareTo(data[j],data[j+1]) > 0) {
                	Person temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                }
            }
        }
    }
	
	public abstract int compareTo(Person a, Person b);
}
package 고객프;

public interface BubbleSorterComparable {
	//비교는 나이, 이름에 따라서 기준이 달라지기 때문에 interface로 만든다. 
	//returns > 0 if a > b
	//returns < 0 if a < b
	//return 0 if a==b
	int compareTo(Person a, Person b);
}