Ch.8 템플릿 메소드 패턴

알고리즘 캡슐화하기

About

이번 챕터에서는 서브클래스에서 언제든 필요할 때마다 알고리즘을 가져다 쓸 수 있도록 캡슐화해본다.

커피와 홍차를 만드는 클래스를 통해 '템플릿 메소드 패턴'을 알아본다.

The Code

Problem

홍차를 만들 때와 커피를 만들 때 해야하는 행동은 비슷하다.

  • 재료를 준비한다.

    • 물을 끓인다.

    • 우려낸다. (홍차의 경우 찻잎을 우려내고, 커피의 경우 필터로 커피를 우려낸다.)

    • 컵에 따라낸다.

    • 첨가물을 넣는다. (홍차의 경우 레몬을 추가하고, 커피의 경우 설탕과 우유를 추가한다.)

그렇다면 홍차와 커피의 상위 인터페이스(추상 클래스)인 CaffeineBeverage를 만들 수 있다.

그런데 물을 끓이는 것과 컵에 따라내는 행동은 완전히 동일하지만 우려내는 행동과 첨가물을 넣는 행동이 조금씩 다르다. 이는 서브클래스인 홍차와 커피에서 재정의해야 한다.

템플릿 메소드 패턴을 이용하면 서브클래스에서 일부 단계를 구현할 수 있다.

Template Method Pattern

템플릿 메소드 패턴(Template Method Pattern)은 알고리즘의 골격을 정의한다.

  • 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있다.

  • 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수 있다.

간단히 말해서 알고리즘의 템플릿(틀)을 만들고, 서브클래스에서 해당 틀의 내용을 구현하는 방식이다.

Hooks

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

이러면 서브클래스는 다양한 위치에서 알고리즘에 끼어들 수 있다. 물론 무시하고 구현하지 않아도 된다.

용도는 다음과 같다.

  • 알고리즘에서 필수적이지 않은 부분을 서브클래스에서 구현하도록 하고 싶을 때

  • 앞으로 일어날 일이나 막 일어난 일에 서브클래스가 반응할 수 있도록 하고 싶을 때

Cautions

템플릿 메소드를 만들 때 추상 메소드가 너무 많아지면 서브클래스에서 일일히 추상 메소드를 구현해야 해서 별로 좋지 않을 수 있다.

  • 알고리즘의 단계를 너무 쪼개지 않는 것도 방법이 될 수 있지만, 알고리즘을 너무 크게 나누면 유연성이 떨어질 수 있어서 잘 결정해야 한다.

  • 필수가 아닌 부분은 후크로 구현하면 부담이 줄 수 있다.

Side Note: Hollywood Principle

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

할리우드 원칙(Hollywood Principle)을 이용하면 의존성 부패(dependency rot)를 방지할 수 있다.

  • 어떤 고수준 구성 요소가 저수준에 의존하고, 그 저수준은 다시 고수준에 의존하는 등... 이렇게 의존성이 꼬여 있는 걸 의존성 부패라고 한다.

  • 즉, 고수준 구성 요소가 저수준 구성 요소에게 "먼저 연락하지 마라. 우리가 연락하겠다." 라고 하는 것과 같다.

템플릿 메소드 패턴을 써서 디자인하면 서브클래스에게 할리우드 원칙을 적용하는 것과 같다.

  • CaffeinBeverage는 고수준 구성요소이며 메소드 구현이 필요한 상황에만 서브클래스를 불러낸다.

  • Tea, Coffee는 호출 당하기 전까지 추상 클래스를 직접 호출하지 않는다.

My Thoughts

어렵게 생각하지 말고 그냥 추상 클래스의 특정 메서드에 구멍을 뚫어두고, 서브클래스에서 해당 부분을 오버라이드해서 구현한다고 보면 될 것 같다.

실제로 많이 쓰는 패턴인 것 같다. 상속 기반의 프레임워크에서 많이 쓰인다고 한다.

스프링 프레임워크의 예를 들자면, FrameworkServlet의 상속을 받은 DispatcherServlet이 있다. FrameworkServlet 추상 클래스는 doService() 추상 메서드가 있고, 이를 상속하는 DispatcherServlet에서 doService() 메서드를 구현한다. 이렇게 해서 FrameworkServletprocessRequest() 메서드에서 중간에 doService() 메서드를 호출하므로, doService()만 구현하는 것으로 로직을 손쉽게 바꿀 수 있는 것이다.

Last updated