블로그 이미지
Kanais
Researcher & Developer 퍼즐을 완성하려면 퍼즐 조각들을 하나 둘씩 맞춰나가야 한다. 인생의 퍼즐 조각들을 하나 둘씩 맞춰나가다 보면 인생이란 퍼즐도 완성되는 날이 오려나...?

calendar

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

Notice

05-05 12:30

Recent Post

Recent Comment

Recent Trackback

Archive

2015. 4. 16. 10:12 Programming/Design Pattern



참고 서적 : GOF의 디자인패턴 / 저자 :  에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스 / 김정아 옮김 / 출판사 : Addison Wesley

 

참고 사이트 : http://en.wikipedia.org/wiki/Template_method_pattern

http://underclub.tistory.com/124

http://thx4alice.tistory.com/342

http://www.dofactory.com/Patterns/PatternTemplate.aspx

 

 

의도

객체의 연산에는 알고리즘의 뼈대만을 정의하고 각 단계에서 수행할 구체적 처리는 서브 클래스 쪽으로 미룹니다. 알고리즘의 구조 자체는 그대로 놔둔 채 알고리즘 각 단계 처리를 서브 클래스에서 재정의할 수 있게 합니다.

 

동기

Application 클래스와 Document 클래스를 제공하는 응용프로그램 프레임워크를 생각해 봅시다. Application 클래스는 파일이나 특정한 외부 형식으로 저장된 문서를 열 수 있고, Document 객체는 파일에서 읽은 문서에 정보를 나타냅니다.

 

특정한 요구나 때에 따라서 프레임워크가 정의한 Application 클래스와 Document 클래스를 상속한 서브 클래스를 정의하여 새로운 응용프로그램을 구축할 수 있을 것입니다. 예를 들어, 그림 그리기 응용프로그램은 DrawApplication 클래스와 DrawDocument 클래스를 정의할 수 있을 것이고, 스프레드시트 응용 프로그램은 SpreadsheetApplication 클래스와 SpreadsheetDocument 클래스를 정의할 수 있을 것입니다.

 

 

추상 클래스인 Application 클래스의 OpenDocument() 연산에는 문서를 열고 읽는 기본 알고리즘이 정의되어 있습니다.

void Application::OpenDocument(const char* name)

        {

            if(!CanOpenDocument(name))

            {

                // 이 문서를 처리하지 못할 때

                return;

            }

            Document* doc = DoCreateDocument();

            if(doc)

            {

                _docs->AddDocument(doc);

                AboutToOpenDocument(doc);

                doc->Open();

                doc->DoRead();

            }

        }

 

OpenDocument()는 문서를 여는 필수적인 절차를 정의합니다. 즉, 열 수 있는 문서인지 확인하여, 응용프로그램마다 필요한 Document 객체를 생성하고, 이를 Document 객체 집합에 추가한 후, 파일에서 문서를 읽어 Document 객체에 싣습니다.

 

이 같은 필수 처리 절차를 정의한 OpenDocument()를 가리켜 템플릿 메서드라고 합니다. 템플릿 메서드는 서브 클래스가 오버라이드할 수 있는 추상 연산을 사용하여 알고리즘을 정의합니다. 즉, OpenDocument() 연산에서 호출하는 각 연산들에 대한 실제 구현은 Application 클래스를 상속하는 서브 클래스가 제공합니다. 템플릿 메서드에서는 열 수 잇는 문서인지 점검하는 일(CanOpenDocument() 연산), 그리고 Document 클래스의 인스턴스를 생성하는 일(DoCreateDocument() 연산) 등을 정의하며, 실제 이 같은 처리가 구체적으로 어떻게 되는지 Application 클래스의 서브 클래스가 정의합니다. 템플릿 메서드는 Application 서브 클래스가 문서를 언제 열어야 하는지를 알 수 있도록 하는 일(AboutToOpenDocument)도 정의합니다.

 

추상 연산을 통해서 알고리즘의 일부 단계를 정의함으로써, 템플릿 메서드는 각 단계의 순서는 고정하되 Application 클래스와 Document 클래스의 서브 크래스는 필요에 따라 이들 단계의 처리를 다양화시킬 수 있도록 합니다.

 

활용성

Template Method Pattern은 다음의 경우에 사용해야 합니다.

-      어떤 한 알고리즘을 이루는 부분 중 변하지 않는 부분을 한 번 정의해 놓고 다양해질 수 있는 부분은 서브 클래스에서 정의할 수 있도록 남겨두고자 할 때

-      서브 클래스 사이의 공통적인 행동을 추출하여 하나의 공통 클래스에 몰아둠으로써 코드 중복을 피하고 싶을 때. 옵다이크(Opdyke)와 존슨이 설명했듯 이는 “일반화를 위한 리팩토링(refactoring to generalize)”의 좋은 예입니다. 먼저, 기존 코드에서 나타나는 차이점을 뽑아 이를 별도의 새로운 연산들로 구분해 놓습니다. 그런 뒤 달라진 코드 부분을 이 새로운 연산을 호출하는 템플릿 메서드로 대체하는 것입니다.

-      서브 클래스의 확장을 제어할 수 있습니다. 템플릿 메서드가 어떤 특정한 시점에 “훅(hook)” 연산을 호출하도록 정의함으로써, 그 특정 시점에서만 확장되도록 합니다.

 

구조

 

참여자

-      AbstractClass(Application) : 서브클래스들이 재정의를 통해 구현해야 하는 알고리즘 처리 단계 내의 기본 연산을 정의합니다. 그리고 알고리즘의 뼈대를 정의하는 템플릿 메서드를 구현합니다. 템플릿 메서드는 AbstractClass에 정의 된 연산 또는 다른 객체 연산뿐만 아니라 기본 연산도 호출합니다.

-      ConcreateClass(MyApplication) : 서브 클래스마다 달라진 알고리즘 처리 단계를 수행하기 위한 기본 연산을 구현합니다.

 

협력방법

ConcreateClass는 AbstractClass를 통하여 알고리즘의 변하지 않는 처리 단계를 구현합니다.

 

결과

템플릿 메서드는 코드 재사용을 위한 기본 기술입니다. 특히 클래스 라이브러리 구현 시 중요한 기술인데, 이는 라이브러리에 정의할 클래스들의 공통 부분을 분리하는 수단이기 때문입니다.

 

템플릿 메서드는 “할리우드 원칙(Hollywood principle)”이라는 역전된 제어 구조를 끌어냅니다. “전화하지 마세요. 우리가 연락할게요(Don’t call us, we’ll call you).”라는 것입니다. 다시 말해, 부모 클래스는 서브 클래스에 정의된 연산을 호출할 수 있지만 반대 방향의 호출은 안 됩니다.

 

템플릿 메서드는 여러 종류의 연산 중 하나를 호출합니다.

-      구체 연산(concrete operation) : ConcreteClass나 사용자 클래스에 정의된 연산

-      AbstractClass 구체 연산 : 서브 클래스에서 일반적으로 유용한 연산

-      기본 연산 : 추상화된 연산

-      팩토리 메서드

-      훅 연산(hook operation) : 필요하다면 서브 클래스에서 확장할 수 있는 기본 행동을 제공하는 연산. 기본적으로는 아무 내용도 정의하지 않습니다.

 

템플릿 메서드 패턴에서는 어떤 연산이 훅 연산인지(오버라이드가 가능한지) 추상 연산인지(꼭 오버라이드해야 하는지)를 지정해 두는 것이 대단히 중요합니다. 훅 연산은 나중에 재정의할 수도 있고, 재정의하지 않을 수도 있는 메서드이고, 추상 연산은 반드시 재정의해야 하는 연산입니다. 추상할 클래스를 효과적으로 재사용하기 위해서, 서브 클래스 작정자는 어떤 연산들이 오버라이드용으로 설계되었는지를 정확하게 이해하고 있어야 합니다.

 

서브 클래스는 부모 클래스에 정의된 연산을 명시적으로 호출하고 또 재정의함으로써 부모 클래스 연산의 행동을 확장합니다.

void DerivedClass::Operation()

        {

            ParentClass::Operation();

            //확장할 내용을 추가합니다.

        }

 

하지만 간혹 상속받은 연산을 호출하는 일을 잊어버립니다. 그래서 이 같은 서브클래스가 부모 클래스의 행동을 확장하는 연산들을 템플릿 메서드로 옮겨 놓음으로써 부모 클래스에게 서브 클래스의 확장을 제어할 수 있는 권한을 부여할 수 있습니다. 이 아이디어는 부모 클래스의 템플릿 메서드에서 훅 연산을 호출하도록 하는 것입니다. 서브 클래스는 이 훅 연산을 재정의할 수 있습니다.

void ParentClass::Operation()

        {

            //부모 클래스가 정의한 행동

            HookOperation();

        }

 

일반적으로는 ParentClass에 정의된 HookOperation은 아무런 행동도 정의하지 않습니다.

 

void ParentClass::HookOperation() { }

 

서브클래스에서는 행동을 확장하려고 Hookoperation()을 재정의합니다.

 

void DerivedClass::HookOperation()

        {

            // 상속받는 클래스가 확장할 코드

        }

 

구현

알아두면 좋을 구현 이슈를 정리해 보았습니다.

1.    C++의 접근 제한 방법을 이용합니다.  C++로 구현할 때, 템플릿 메서드에서 호출하는 기본 연산들을 protected 멤버로 구현합니다. 이렇게 하면 이 연산들을 템플릿 메서드만 호출할 수 있게 됩니다. 오버라이드해야 하는 기본 연산은 반드시 순수 가상 함수로 정의합니다. 템플릿 메서드 자체는 재정의되면 안됩니다. 따라서 템플릿 메서드는 비가상 멤버 함수로 만듭니다.

2.    기본 연산의 수를 최소화합니다.  템플릿 메서드를 구현하는 중요한 목적 중 하나는 서브클래스가 알고리즘을 실체화하기 위해 오버라이드해야 하는 기본 연산의 개수를 줄이는 것입니다. 재정의가 필요한 연산의 수가 많아질수록 사용자에게는 지겨운 일만 늘어납니다.

3.    이름을 짓는 규칙을 만듭니다.  재정의가 필요한 연산은 식별이 잘 되도록 접두사를 붙이는 것이 좋습니다. 예를 들어, 메킨토시 응용프로그램 제작용 MacApp 프레임워크에서 모든 템플릿 메서드는 “DoCreateDocument”, “DoRead” 등 “Do”로 시작하도록 이름을 지었습니다.

 

예제 코드

이제부터 볼 C++ 예제는 부모 클래스가 서브클래스에게 변화하지 않는 절차를 따르게 하는 방법을 보여줍니다. 이 예제는 NeXT의 AppKit에서 따왔습니다. 화면 그리기 기능을 지원하는 View 클래스를 생각해 봅시다. View 클래스는 그림 그리기에 필요한 변하지 않는 사항을 정해 두며, 서브클래스들은 이 사항을 따라 자신의 “포커스(focus)”를 받은 후에만 그리기를 수행할 수 있습니다. 이 포커스는 특정한 상태(색깔이나 글꼴 등)의 조건이 적절히 만족될 때 주어집니다.

 

이런 상태를 마련하는 데에 Display() 템플릿 메서드를 사용할 수 있습니다. View는 두 개의 구체 연산인 SetFocus()와 ResetFocus()를 정의해 두어, View 클래스의 그리기 상태를 설정하거나 해제할 수 있도록 합니다. View 클래스의 DoDisplay() 연산은 훅 연산으로서, 실제적인 그리기를 구현합니다. Display()는 DoDisplay()전에 SetFocus()를 호출하여 그리기 상태를 설정합니다. 그리고 이후에는 그리기 상태를 해제하기 위해 ResetFocus()를 호출합니다.

void View::Display()

        {

            setFocus();

            DoDisplay();

            ResetFocus();

        }

 

이런 조건을 유지하려면 View 사용자는 Display()만을 호출할 수 있어야 합니다. View 서브클래스들은 DoDisplay()를 재정의할 수 있습니다.

 

일반적으로 View 클래스에 정의된 DoDisplay()는 아무런 일을 하지 않습니다.

void View::DoDisplay() {  }

 

서브클래스는 이 연산을 오버라이드하여 특정한 그리기 행동을 추가합니다.

        void MyView::DoDisplay()

        {

            // 필요한 기능

        }

 

 

잘 알려진 사용예

템플릿 메서드는 거의 모든 추상 클래스에서 사용할 정도로 필수적이고 기본적인 패턴입니다. 워프스-브록 및 공저자들(Wirfs-Brock et al.)은 템플릿 메서드에 대한 전반적인 설명과 논의를 훌륭하게 해 놓았습니다.

'Programming > Design Pattern' 카테고리의 다른 글

[Design Pattern] Bridge Pattern  (0) 2015.04.16
[Design Pattern] Builder  (0) 2015.04.16
[Design Pattern] Abstract Factory  (0) 2015.04.16
posted by Kanais