FIF's 코딩팩토리

템플릿 메소드 패턴(Template Method Pattern) 정리 본문

Back-End/Design Pattern(디자인 패턴)

템플릿 메소드 패턴(Template Method Pattern) 정리

FIF 2019. 6. 5. 17:01
반응형

템플릿 메소드 패턴(Template method pattern) 정의

메소드에서 알고리즘의 골격을 정의한다.

알고리즘의 여러 단계 중, 일부는 서브클래스에서 구현할 수 있다.

템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를

재정의 할 수 있다.

 

알고리즘의 틀을 만들기 위한 패턴이다.(알고리즘 캡슐화)

이 패턴에서 틀(템플릿)이란, 일련의 단계들로 알고리즘을 정의한 메소드다.

여러 메소드 가운데 하나 이상의 추상 메소드로 정의되며, 그 추상메소드는 서브클래스에서 구현된다.

이렇게 하면 서브클래스에서 일부분의 단계를 구현할 수 있도록 하면서도 알고리즘의 구조는 바꾸지 않아도 된다.

 

템플릿 메소드 패턴 클래스 다이어그램

 

커피와 차가 만들어지는 과정 비교

1.     커피 만드는 법

1)    물을 끓인다.

2)    끓는 물에 커피를 우려낸다.

3)    커피를 컵에 따른다

4)    설탕과 우유를 추가한다.

 

2.     홍차 만드는 법

1)    물을 끓인다.

2)    끓는 물에 차를 우려낸다.

3)    차를 컵에 따른다.

4)    레몬을 추가한다.

 

대부분의 과정이 비슷하다.(알고리즘이 똑같나?)

 

Coffer.java

public class Coffee {
	void prepareRecipe() {
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}
	public void boilWater() {
		System.out.println("물 끓이는 중");
	}
	public void breqCoffeeGrinds() {
		System.out.println("필터를 통해 커피를 우려내는 중");
	}
	public void pourInCup() {
		System.out.println("컵에 따르는 중");
	}
	public void addSugarAndMilk() {
		System.out.println("설탕과 우유를 추가하는 중");
	}
}

 

Tea.java

public class Tea {
	void prepareRecipe() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}
	public void boilWater() {
		System.out.println("물 끓이는 중");
	}
	public void steepTeaBag() {
		System.out.println("차를 우려내는 중");
	}
	public void pourInCup() {
		System.out.println("컵에 따르는 중");
	}
	public void addLemon() {
		System.out.println("레몬을 추가하는 중");
	}
}

 

코드의 중복은 되도록이면 피해야 한다.

Coffee 하고 Tea 클래스가 거의 똑같으니까 공통적인 부분을 추상화시켜 클래스로 만든다.

 

추상화 하기.

이와 같은 디자인이 괜찮아 보일 수 있으나 조금만 더 생각해보면, Coffee와 Tea를 만드는 알고리즘이 똑같다는 걸 알 수 있다.

 

1.    물을 끓인다.

2.    뜨거운 물을 이용하여 커피 또는 홍차를 우려낸다.

3.    만들어진 음료를 컵에 따른다.

4.    각 음료에 맞는 첨가물을 추가한다.

 

 

 

 

 

 

prepareRecipe() 추상화하기.

Coffee

Tea

void prepareRecipe(){

boilWater();

brewCoffeeGrimds();

poulnCup();

addSugarAndMilk();

}

void prepareRecipe(){

boilWater();

steepTeaBag();

pourlnCup();

addLemon();

}

 

Coffee와 Tea에서 서로 다른 메소드를 사용하고 있는 것처럼 보이지만, 사실 커피를 우려내는 것과 홍차를 우려내는 것은 별반 차이가 없다.

마찬가지로 커피에 설탕과 우유를 추가하는 것과 홍차에 레몬을 추가하는 것은 같은 알고리즘이라 할 수 있다.

void prepareRecipe() {
		boilWater();
		brew();
		poulnCup();
		addCondiments();
	}

 

템플릿 메소드 패턴 사용하기.

CaffeineBeverage.java

	public abstract class CaffeineBeverage {
		void final prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addcondiments();
		}
		abstract void brew();
		abstract void addcondiments();
		void boilWater() {
			System.out.println("물 끓이는 중");
		}
		void pourInCup() {
		System.out.println("컵에 따르는 중");
		}
	}

 

Coffee.java

public class Coffee extends CaffeineBeverage {
		@Override
		void brew() {
			System.out.println("필터를 통해 커피를 우려내는 중");
		}
		@Override
		public void addCondiments() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
	}

 

Tea.java

public class Tea extends CaffeineBeverage {
		@Override
		void brew() {
			System.out.println("차를 우려내는 중");
		}
		@Override
		public void addCondiments() {
			System.out.println("레몬을 추가하는 중");
		}
	}

 

차 만들기.

1. Tea 객체를 만들고.

Tea myTea = new Tea();

 

2. 템플릿 메소드를 호출한다.

myTea.prepareRecipe(); 카페인 음료를 만들기 위한 알고리즘이 돌아간다.

 

3. 물을 끓인다.

boilWater(); 이 단계는 CaffeineBeverage에서에서 처리된다.

 

4. 이제 차를 우려낸다. 

brew(); 이방법은 서브클래스만 알고있다.

 

5. 차를 컵에 따른다.

pourInCup(); 이 단계도 공통적인 부분이기 때문에 Caffeinebeverage에서에서 맡아서 처리된다.

 

6. 마지막으로 첨가물을 추가한다.

addCondiments(); 첨가물은 음료마다 다르기 때문에서브클래스에서 처리된다.

이런 식으로 템플릿 메소드에서는 알고리즘의 각 단계들을 정의하며, 그중 한 개 이상의 단계가 서브클래스에 의해 제공될 수 있다.

 

 

 

 

처음 Tea, Coffee 클래스 템플릿 메소드 패턴을 적용한 Tea, Coffee 클래스

Coffee와 Tea가 각각 작업을 처리, 두 클래스에서 각자 알고리즘을 수행한다.

 

Coffee와 Tea에 중복된 코드가 있다.

 

 

알고리즘이 바뀌면 서브클래스를 일일이 열어서 여러군대를 고쳐야 한다.

 

클래스 구조상 새로운 음료를 추가하려면 꽤 많은 작업을 해야 한다.

 

 

 

알고리즘에 대한 지식과 구현 방법이 여러클래스에 분산되어 있다.

CaffeinBeverage 클래스에서 작업을 처리, 알고리즘을 혼자 독점한다.

 

CaffeinBeverage 덕분에 서브클래스에서 코드를 재사용할 수 있다.

 

알고리즘이 한 군데에 모여있기 때문에 그 부분만 고치면 된다.

 

다른 카페인 음료도 쉽게 추가할 수 있는 프레임워크를 제공한다.

 

카페인 음료를 추가할 때 몇가지 메소드만 추가하면 된다.

 

CaffeineBeverage 클래스에 알고리즘에 대한 지식이 집중되어 있으며 일부 구현만 서브클래스에 의존한다.

 

템플릿 메소드와 후크

후크(hook)는 추상 클래스에 선언되는 메소드이긴 하지만, 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드 이다.

후크를 사용하면 서브클래스 입장에서 다양한 위치에서 알고리즘을 적용할 수 있다.

 

후크 사용법

	public abstract class CaffeineBeverageWithHook {
		void final prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		if ( customerWantsCondiments() ) {
		addcondiments();
	}
		abstract void brew();
		abstract void addcondiments();
		void boilWater() {
			System.out.println("물 끓이는 중");
	}
		void pourInCup() {
		System.out.println("컵에 따르는 중");
	}
		// 이 메소드는 서브클래스에서 필요에 따라 오버라이드 할 수 있는 메소드 이므로 후크
		boolean customerWantsCondiments() {
			return true;
	}
}

customerWantsCondiments()메소드는 별 내용 없는 기본 메소드를 구현해 놓은 후크 메소드 이다.

 

서브클래스에서 오버라이드 하여 후크 사용하기.

	public class CoffeeWithHook extends CaffeineBeverageWithHook {
		@Override
		void brew() {
			System.out.println("필터를 통해 커피를 우려내는 중");
		}
		@Override
		public void addCondiments() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
		@Override
		public boolean customerWantsCondiments() {
			String answer = getUserInput();
			if (answer.toLowerCase().startWith("y"))
				return true;
			else
				return false;
		}
		private String getUserInput() {
			// 입력받는 로직
		}
	}

이처럼 알고리즘에서 필수적이지 않은 부분을 필요에 따라 선택적으로 서브클래스에서 구현하든 말든 내버려 두는 경우 hook를 사용할 수 있다.

 

할리우드 원칙과 템플릿 메소드 패턴

디자인 원칙

먼저 연락하지 마세요, 저희가 연락드리겠습니다.

 

이 디자인 원칙을 활용하면 의존성 부패(Dependency rot)를 방지할 수 있다.

어떤 고수준 구성요소가 저수준 구성요소에 의존하고, 그 저수준 구성요소는 다시 고수준 구성요소에 의존하고, 그 고수준 구성요소는 다시 또 다른 구성요소에 의존하고… 이런 식으로 의존성이 복잡하게 꼬여 있는 것을 의존성 부패라고 한다.

 

할리우드 원칙을 사용하면 저수준 구성요소에서 시스템에 접속을 할 수는 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정한다.

즉, 저수준 구성요소는 컴퓨테이션에 참여할 순 있지만, 절대 고수준 구성요소를 직접 호출하면 안 된다.

 

 

CaffeineBeverage는 고수준 구성 요소이다.음료를 만드는 방법에 해당하는 알고리즘을 장악하고 있고, 메소드 구현이 필요한 상태에서만 서브클래스를 불러낸다.

 

Coffee와 Tea와 같은 서브클래스들은 각각의 자잘한 메소드 구현을 제공하기 위한 용도로만 쓰인다.

이와 같은 서브클래스들은 고수준 구성요소인 CaffeeineBeverage 클래스로부터 호출을 당하기 전까지는 절대로 CaffeeineBeverage 추상 클래스를 직접 호출하지 않는다.

 

이렇게 함으로써 CaffeineBeverage 클래스의 클라이언트에서는 Tea나 Coffee 같은 구상 클래스가 아닌 CaffeineBeverage에 추상화되어 있는 부분에 의존하게 된다.

그렇게 함으로써 전체 시스템의 의존성이 줄어들 수 있다.

반응형
Comments