일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 베드테이블
- 목적중심리더십
- 면접
- 커스텀린트
- 끝말잇기
- 브런치작가되기
- 좌식테이블
- 북한살둘레길
- 어떻게 나답게 살 것인가
- 리얼하다
- 함수형 프로그래밍
- 한달어스
- 한달독서
- 1일1커밋
- 한달브런치북만들기
- 지지않는다는말
- 테트리스
- 아비투스
- 한단어의힘
- 재택근무
- 베드트레이
- 목적 중심 리더십
- 소프시스
- T자형인재
- 캐치마인드
- 소프시스 밤부 좌식 엑슬 테이블
- 자취필수템
- 프래그먼트
- 안드로이드
- 슬기로운 온라인 게임
- Today
- Total
정상에서 IT를 외치다
[디자인패턴] 템플릿 메소드 패턴 본문
안녕하세요. 블랙진입니다.
책 HeadFirstDesignPattern 을 보며 코드를 Kotlin 으로 바꿔가며 공부한 내용입니다.
템플릿 메소드 패턴
메소드에서 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있습니다. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 할 수 있습니다.
예제
우리는 카페인이 들어간 음료, 그중 커피와 티를 만드는 예제를 살펴보겠습니다.
class Coffee {
fun prepareRecipe() {
boilWater()
brewCoffeeGrinds()
pourInCup()
addSugarAndMilk()
}
private fun boilWater() {
println("물 끓이는 중")
}
private fun brewCoffeeGrinds() {
println("필터를 통해서 커피를 우려내는 중")
}
private fun pourInCup() {
println("컵에 따르는 중")
}
private fun addSugarAndMilk() {
println("설탕과 우유를 추가하는 중")
}
}
커피는 물을 끓이고 -> 커피를 우려 내고 -> 컵에 따르고 -> 설탕과 우유를 추가해 줍니다.
class Tea {
fun prepareRecipe() {
boilWater()
steepTeaBag()
pourInCup()
addLemon()
}
private fun boilWater() {
println("물 끓이는 중")
}
private fun steepTeaBag() {
println("차를 우려내는 중")
}
private fun pourInCup() {
println("컵에 따르는 중")
}
private fun addLemon() {
println("레몬을 추가하는 중")
}
}
티는 물을 끓이고 -> 차를 우려내고 -> 컵에 따르고 -> 레몬을 추가해 줍니다.
커피와 티를 만드는 과정을 보니 겹치는 부분이 있습니다. 바로 물을 끓이는 부분과 컵에 따르는 부분입니다. 여기서 우리는 공통된 부분을 찾았으니 이를 추상화 함으로써 구조를 개선할 수 있습니다.
개선1 - 공통된 부분 추상화하기
abstract class CaffeineBeverage {
abstract fun prepareRecipe()
fun boilWater() {
println("물 끓이는 중")
}
fun pourInCup() {
println("컵에 따르는 중")
}
}
추상 클래스인 CoffineBaverage 를 생성한 후 boilWater() 와 pourInCup() 을 구현해 줍니다. 이렇게 해줌으로써 커피와 차에서는 더이상 공통된 메소드를 구현해 주지 않아도 됩니다. 하지만 이러한 방법은 커피와 차를 만드는 법을 아는 저희에게 적절한 방법입니다. 커피를 만들기 위해서는 물을 끓이고 -> 커피를 우려 내고 -> 컵에 따르고 -> 설탕과 우유를 추가해 줍니다. 이 순서를 따라야 하는데 위 코드에서는 알 수 있는 방법이 없습니다. 또한 차를 만드는 방법 또한 커피를 만드는 그 순서 다시말해 그 알고리즘은 같습니다. 이에 이러한 순서 혹은 틀을 만들어서 코드를 더 개선해 보겠습니다.
abstract class CaffeineBeverage {
fun prepareRecipe() {
boilWater()
brew()
pourInCup()addCondiments()
}
abstract fun brew()
abstract fun addCondiments()
private fun boilWater() {
println("물 끓이는 중")
}
private fun pourInCup() {
println("컵에 따르는 중")
}
}
CaffeineBeverage 에서는 물을 따르고 -> 뭔가를 끓이고 -> 컵에 따르고 -> 뭔가를 첨가 해준다 라는 커피와 차를 만드는 공식 즉 템플릿을 만들어 주었습니다. 이렇게 함으로서 차와 커피를 만드는 순서를 모르는 사람도 누구든지 위 템플릿을 가지고 만들 수 있게 됩니다.
문제
우리는 템플릿을 만들어 줌으로써 차와 커피를 멋지게 구현하였습니다. 하지만 손님이 커피를 만드는 마지막 단계에서 설탕와 우유를 넣고 싶지 않은 경우에는 어떻게 해야 될까요? 이와 같은 경우 템플릿에서 알고리즘 순서 중 일부를 선택하여 구현할 수 있는 방법이 있습니다!
후크
후크는 추상 클래스에서 선언되는 메소드긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드입니다. 이렇게 하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있습니다. 물론 그냥 무시하고 넘어갈 수도 있죠.
개선3 - 후크를 사용해 선택적으로 첨가물을 추가하도록 하자
abstract class CaffeineBeverage {
fun prepareRecipe() {
boilWater()
brew()
pourInCup()
/**
* 후크
*
* 알고리즘의 특정 부분이 선택적으로 적용된다든가 하는 경우에는 후크를 쓰면 되죠.
* 후크를 쓰면 서브클래스에서 필요한 경우에 후크를 구현할 수도 있지만, 반드시 구현해야 하는건 아니니까요.
*/
if(customerWantsCondiments()) {
addCondiments()
}
}
abstract fun brew()
abstract fun addCondiments()
private fun boilWater() {
println("물 끓이는 중")
}
private fun pourInCup() {
println("컵에 따르는 중")
}
open fun customerWantsCondiments() = true
}
addCondiments 부분을 open fun customerWantsCondiments() 의 Boolean 값에 의해 실행 할지 말지를 선택할 수 있게 변경하였습니다. 이를 커피 예제에서는 어떻게 구현되는지 확인해 보겠습니다.
class CoffeeWithHook : CaffeineBeverage() {
override fun brew() {
println("필터를 통해서 커피를 우려내는 중")
}
override fun addCondiments() {
println("설탕과 우유를 추가하는 중")
}
override fun customerWantsCondiments(): Boolean {
val answer = getUserInput()
return answer.toLowerCase().startsWith("y")
}
private fun getUserInput(): String {
val answer: String?
println("커피에 우유와 설탕을 넣어 드릴까요? (y/n) ")
val `in` = BufferedReader(InputStreamReader(System.`in`))
answer = `in`.readLine()
return answer ?: "no"
}
}
getUserInput 에서 InputStreamReader를 사용해 사용자의 인풋을 받아 첨가물 추가 여부를 확인할 수 있게 되었습니다.
fun main() {
val coffeeWithHook = CoffeeWithHook()
coffeeWithHook.prepareRecipe()
}
Main 에서는 위와 같이 구현합니다.
디자인 원칙
할리우드 원칙
먼저 연락하지 마세요. 저희가 연락 드리겠습니다.
할리우드 원칙을 사용하면, 저수준 구성요소에서 시스템에 접속을 할 수 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 됩니다. 즉, 고수준 구성요소에서 저수준 구성요소에게 "먼저 연락하지 마세요. 제가 먼더 연락 드리겠습니다." 라고 얘기를 하는 것과 같죠.
누가 무엇을 할까요?
템플릿 메소드 패턴
- 알고리즘의 일부 단계를 구현하는 것을 서브클래스에서 처리합니다.
스트레티지 메소드 패턴
- 바꿔 쓸 수 있는 행동을 캡슐화하고, 어떤 행동을 사용할지는 서브클래스에 맡깁니다.
팩토리 메소드 패턴
- 어떤 구상 클래스를 생성할지를 서브클래스에서 결정합니다.
'디자인패턴' 카테고리의 다른 글
[디자인패턴] 스테이트 패턴 (0) | 2019.06.20 |
---|---|
[디자인패턴] 이터레이터와 컴포지트 패턴 (2) | 2019.06.13 |
[디자인패턴] 어댑터패턴과 퍼사드패턴 (1) | 2019.05.19 |
[디자인패턴] 커맨드패턴 (0) | 2019.05.18 |
[디자인패턴] 팩토리패턴 (0) | 2019.05.13 |