본문 바로가기

Java Programming/Java

Template Method 패턴

템플릿이란 무엇인가?

템플릿이란 문자 모양으로 구멍이 뚫려있는 얇은 플라스틱 판을 말합니다.

그 구멍을 따라 펜으로 그리면 손으로도 반듯한 문자를 쓸 수 있습니다.

템플릿의 구멍을 보면 어떤 모양의 문자인지는 알 수 있지만, 실제로 어떤 문자가 될지는 필기구에 의해 결정됩니다.

펜을 사용하면 펜으로 쓴 문자가 되고, 연필을 사용하면 연필로 쓴 문자가 됩니다.

그러나 어떤 필기구를 사용해도 쓰여진 문자는 템플릿 구멍의 형태와 동일합니다.


Template Method 패턴이란

이번 장에서 배울 Template Method 패턴은 템플릿의 기능을 가진 패턴입니다.

상위 클래스쪽에 템플릿에 해당하는 메소드가 정의되어 있고, 그 메소드의 정의 안에는 추상 메소드가 사용되고 있습니다.

따라서 상위 클래스의 프로그램만 보면 추상 메소드를 어떻게 호출 하고 잇는지 알 수 있지만, 최종적으로 어떤 처리가 수행되는지는 알 수 없습니다.


추상 메소드를 실제로 구현하는 것은 하위 클래스입니다.

하위 클래스 측에서 메소드를 현하면 구체적인 처리가 결정됩니다.

서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 실행될 것입니다.

그러나 어떤 하위 클래스에서 어떤 구현을 하더라도 처리의 뼈대를 결정하고,

하위 클래스에서 그 구체적인 내용을 결정하는 디자인 패턴을 Template Method 패턴이라고 부릅니다. 


Template Method 패턴을 사용한 예제 프로그램을 살펴봅시다.

여기에서 작성할 예제 프로그램은 ' 문자나 문자열을 5회 반복해서 표시하기' 입니다.

여기에서는 AbstractDisplay, CharDisplay, StringDisplay, Main 이라는 네 가지 클래스가 등장합니다.

AbstractDisplay 클래스에서는 display 메소드가 정의되어 있고

display 메소드 안에서는 open. print, close 라는 세 가지 메소드가 사용되고 있습니다.

Open, print, close 라는 세 개의 메소드도 AbstractDisplay 클래스 안에서 선언되고있지만 실체가없는 추상메소드입니다.

여기에서는 추상 메소드를 사용하고 잇는 display 메소드가 템플릿 메소드가 됩니다.

open, print, close 메소드를 실제로 구현하고 있는 것은 AbstractDisplay 클래스의 하위 클래스인 CharDisplay 클래스나 StringDisplay 클래스 입니다.

Main 클래스는 동장 테스트를 위한 클래스입니다.


AbstractDisplaysy      -         메소드 display만 구현되고 있는 추상 클래스

CharDisplay            -         메소드 Open, Print, Close 를 구현하고 있는 클래스

StringDisplay          -         메소드 Open, Print, Close 를 구현하고 있는 클래스

Main                   -         동작 테스트용 클래스


AbstractDisplay 클래스

AbstractDisplay 클래스는 open, print, close, display 라는 메소드를 가지고 있습니다.

이중에서 open, print, close 는 추상 메소드이고 display 메소드만이 구현되고 있습니다.

AbstractDisplay 클래스에 쓰여있는 display 메소드의 정의를 읽어 보면

다음과 같은 처리를 실행하고 있습니다.


open 메소드를 호출

print 메소드를 5회 호출

close 메소드를 호출


그러면 open, print, close의 각 메소드는 무엇을 하고 있을까요?

AbstractDisplay 클래스를 읽어 보면, 이 세 가지 메소드는 추상 메소드이기 때문에

AbstractDisplay가 '실제로 무엇을 하고 있는지 AbstractDisplay 클래스만 보고서는 알 수 없습니다.

실제로 무엇을 하고 있는가는 open,print, close 를 구현하는 하위 클래스에게 맡기고 있습니다.


Public abstract class AbstractDisplay {


         public abstract void open();

         public abstract void print();

         public abstract void close();

         

         public final void display()  {

                open();

                for (int i = 0; i < 5; i++)  {

                      print();

                }

                 close();

         }

]


CharDisplay 클래스

여기까지 이해가 되었다면 이번에는 하위 클래스의 하나인 CharDisplay 클래스를 살펴봅시다.

상위 클래스인 AbstractDisplay 클래스에서 추상 메소드였던 open, print, close 가 모두 구현되어 있기 때문에

CharDisplay 클래스는 추상 클래스가 아닙니다.

CharDisplay 클래스의 open, print, close 는 다음과 같은 처리를 실행하고 있습니다.


open        :       문자열 "<<" 표시한다

print       :       생성자에서 주어진 1문자를 표시한다

close       :       문자열 ">>" 을 표시한다.


이 상태에서 display 메소드를 호출된다면 어떻게 될까요?

예를 들어 생성자에 'H' 라는 문자가 전달되었다고 가정하면

<<HHHHH>> 라는 문자열이 표시됩니다. 


public class CharDisplay extends AbstractDisplay  {


        private char ch;

        public CharDisplay (char ch)  {

                 this.ch = ch;

       }


       public void open()  {

            System.out.print("<<");

       }


       public void print()   {

              System.out.print(ch);

        }


        public void close()  {

           System.out.print(">>");

        }

}


StringDisplay 클래스

그러면 또 하나의 하위 클래스인 StringDisplay 클래스를 살펴 봅시다.

물론 여기에서도 open, print, close 가 구현되고 있습니다.

이번에는 어떤 처리를 수행하고 있을까요?

StringDisplay의 open, print, close는 다음과 같은 처리를 실행하고 있습니다.

이 상태에서 display 메소드가 호출된다면 어떻게 될까요?

생성자에 "Hello, world" 라는 문자열이 전달되어 있다고 가정하면

----------------

ㅣHello, world.ㅣ


ㅣHello, world.ㅣ

ㅣHello, world.ㅣ

ㅣHello, world.ㅣ

----------------

와 같이 테두리에 둘러싸인 문자열이 표시됩니다.


open          :       문자열 " +--------+"을 표시한다

print         :       생성자에서 주어진 문자열을 "ㅣ" 와 "ㅣ" 사이에 표시한다.

close         :       문자열 " +--------+을 표시한다.


public class StringDisplay extends AbstractDisplay {

        

         private String string;

         private int width;

         pubilc StringDisplay (String string)  {

                this.string  = string;

                this.width   = string.getBytes().length;

         }


         public void open()  {

              printLine();

         }


         public void print()   {

               System.out.println("l" + string + "l");

         }


         public void close()  {

              printLine();

        }


        private void printLine()  {

                 System.out.print("+");

                 for(int i= 0; i< width; i++) {

                      System.out.print("-");

                 }

                System.out.println("+");

       }

}


Main 클래스

Main 클래스는 동작 테스트를 실행합니다.

지금까지 만들었던 CharDisplay 클래스와 StringDisplasy 클래스의 인스턴스를 만들어 display 메소드를 호출하고 있습니다.


public class Main  {

          public static void main(String[] args)   {

               AbstractDisplay d1 = new CharDisplay('H');

               AbstractDisplay d2 = new StringDisplay("hello, world.");

               AbstractDisplay d3 = new StringDisplay("안녕하세요.");

              d1.display();

              d2.display();

              d3.display();

          }

}


Template Method 패턴의 등장인물

 AbstractClass(추상 클래스)의 역할

 AbstractClass 는 템플릿 메소드를 구현합니다.

또한 그 템플릿 메소드에서 사용하고 있는 추상 메소드를 선언합니다.

이 추상 메소드는 하위 클래스인 ConcreteClass 역할에 의해 구현됩니다.

예제 프로그램에서는 AbstractDisplay 클래스가 이 역할을 합니다.


ConcreteClass(구현 클래스)의 역할

AbstractClass 역할에서 정의되어 있는 추상 메소드를 구체적으로 구현합니다.

여기에서 구현한 메소드는 AbstractClass 역의 템플릿 메소드에서 호출됩니다.

예제 프로그램에서는 CharDisplay 클래스나 StringDisplay 클래스가 이 역할을 합니다.


로직을 공통화할 수 있다.

Template Method 패턴을 사용하면 어떤 이점이 있을까요?

상위 클래스의 템플릿 메소드에서 알고리즘이 기술되어 있으므로, 

하위 클래스측에서는 알고리즘을 일일이 기술할 필요가 없습니다.

예를 들어 Template Method 패턴을 사용하지 않고, 

에디터의 복사 & 붙여넣기 기능을 사용해서 복수의ConcreteClass 역할을 만들었다고 가정합시다.

ConcreteClass1, ConcreteClass2, ConcreteClass3 은 모두 비슷하지만 다른 클래스가 됩니다.

작성한 직후에는 괜찮짐나 나중에 ConcreteClass1 에 버그가 발견되면 대체 어떻게 될까요?

1개의 버그를 수정하기 위해 모든 ConcreteClass를 수정해야 됩니다.

그런점에서 Template Method 패턴으로 프로그래밍을 하면 템플릿 메소드에 오류가 발견되더라고  템플릿 메소드만 수정하면 됩니다.


상위 클래스와 하위 클래스의 연계

Template Method 패턴에서는 상위 클래스와 하위 클래스가 긴밀하게 연락을 취하며 작동하고 있습니다.

따라서 상위 클래스에서 선언된 추상 메소드를 실제로 하위 클래스에서 구현할 때에는 

그 메소드가 어느 타이밍에 호출되는지 이해해야 합니다.

상위 클래스와 소스 프로그램이 없으면 하위 클래스의 구현이 어려울 수도 있습니다.


하위 클래스를 상위 클래스와 동일시한다.

예제 프로그램 안에서는 CharDisplay의 인스턴스도, StringDisplay 의 인스턴스도 AbstractDisplay형의 변수에 대입하고 있습니다.

그리고 display 메소드를 호출하고 잇습니다.

상위 클래스형의 변수가 있고, 그 변수에 하위 클래스형의 인스턴스가 대입된다고 가정합니다.

이떄 instanceof 등으로 하위 클래스의 종류를 특정하지 않아도 프로그램이 작동하도록 만드는 것이 좋습니다.


"상위 클래스형의 변수에 하위 클래스의 어떤한 인스턴스를 대입해도 제대로 작송할 수 있도록 한다" 는 원칙은 lsp이라고 불립니다.,

이 LSP는 Template Method 패턴에 국한되지 않는 상속의 일반적인 원칙입니다.