Notice
Recent Posts
Recent Comments
«   2024/05   »
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
Archives
Today
Total
관리 메뉴

DeFacto-Standard IT

Template Method Pattern (기본) 본문

Design Pattern/Behavioral Pattern

Template Method Pattern (기본)

defacto standard 2017. 9. 26. 11:25

1. 가정

 - 로직1, 로직2, 고유메서드, 로직3 의 순서로 출력하는 클래스를 2개 만든다.

 - 고유메서드를 호출하면, 현재 클래스의 이름을 출력한다.

 - 로직1, 2, 3은 모든 클래스가 동일한 로직을 수행한다.




2. Naive Code

 - NaiveConcreteClass1, 2


public class NaiveConcreteClass1 {

public void call(){
logic1();
logic2();
uniqueMethod();
logic3();
}

private void logic1(){
System.out.println("logic1()");
}
private void logic2(){
System.out.println("logic2()");
}
private void logic3(){
System.out.println("logic3()");
}
private void uniqueMethod(){
System.out.println("This is NaiveConcreteClass1");
}
}
public class NaiveConcreteClass2 {

public void call(){
logic1();
logic2();
uniqueMethod();
logic3();
}

private void logic1(){
System.out.println("logic1()");
}
private void logic2(){
System.out.println("logic2()");
}
private void logic3(){
System.out.println("logic3()");
}
private void uniqueMethod(){
System.out.println("This is NaiveConcreteClass2");
}
}


로직1,2,3을 정의하고, 고유메서드는 자신의 클래스 이름을 출력하는 NaiveConcreteClass 2개를 정의한다. 가정한대로 로직1, 2, 고유메서드, 3 의 순서로 기능을 수행한다.

- NaiveClient

public class NaiveClient {
public static void main(String args[]) {
NaiveConcreteClass1 naiveConcreteClass1 = new NaiveConcreteClass1();
NaiveConcreteClass2 naiveConcreteClass2 = new NaiveConcreteClass2();

naiveConcreteClass1.call();
System.out.println();
naiveConcreteClass2.call();
}
}

2개의 클래스를 선언하고, 객체를 만들어서 call()을 통해 가정한 기능(로직1,2,고유메서드,3 출력)을 수행한다.



3. 문제점


현재 NaiveConcreteClass1, 2는 모두 로직1, 2, 고유메서드, 3 의 순서로 실행이 된다.


하지만 요구사항 1번이 다음과 같이 변경된다고 해보자.

 - 로직1, 고유메서드, 2, 3의 순서로 실행이 된다.


여기서 변경해야할 것은 2가지이다.

1. NaiveConcreteClass1의 logic2()와 uniqueMethod()의 호출 순서를 바꾼다.

2. NaiveConcreteClass2의 logic2()와 uniqueMethod()의 호출 순서를 바꾼다.



위에서는 2가지 경우일 뿐이다. 그러나 NaiveConcreteClass가 N개 있다면?

1. NaiveConcreteClass1의 logic2()와 uniqueMethod()의 호출 순서를 바꾼다.

2. NaiveConcreteClass2의 logic2()와 uniqueMethod()의 호출 순서를 바꾼다.

...

N. NaiveConcreteClass2N logic2()와 uniqueMethod()의 호출 순서를 바꾼다.


위와 같은 노가다를 수행하게 된다. 


만약, 중간에 하나라도 실수해서 잘못 바꾼다면, 어떤 클래스가 바뀌지 않았는지 N번 찾아야한다.

아니면 제대로 작성된 클래스를 N-1번 복사해서 내용을 N-1번 고치던지..


이런 경우에도 심각한 노가다가 필요하다.


'산탄총 수술'이라는 것이 바로 이런 경우에 해당한다.


4. 해결방안


현재 이러한 문제가 일어나는 이유는 소스코드의 중복 때문이다.


현재 모든 클래스가 동일한 로직의 플로우를 따르고 있으므로 이를 추상화 시킬 필요가 있다.


추상화시킨 클래스의 순서만 변경된다면, 아래 딸려있는 모든 클래스는 이 순서를 따른다.


만약, 특정 클래스는 다른 순서를 따른다면, 이 클래스는 추상 클래스를 상속받으면 안된다.



그러나 여기서 문제가 하나 생긴다. 


'유니크메서드'이다.


로직 1, 유니크메서드, 로직2, 로직3의 플로우를 N개의 클래스가 모두 따르므로 이런 플로우를 따르는 추상화클래스를 만들면 문제가 안된다.


하지만 로직 1, 2, 3의 내용은 N개의 클래스 모두 동일하나 유니크 메서드는 자신의 클래스 이름을 출력해야하므로, N개의 클래스가 모두 다르다.


이를 해결할 수 있는 방법은, Abstract Method이다. 

이런 메서드가 있다는 것만 선언하고, 실제 구현은 하위 클래스에게 맡기는 것이다.


Template Method라는 단어의 뜻은 '틀 메서드'라는 의미이다.


예제에서는 로직1, 유니크메서드, 로직2, 로직3 을 수행하는 메서드 자체가 모두에게 동일하게 적용되므로 Template라는 말을 쓴다.


여기서 로직1,2,3의 내용과 플로우가 '틀'이 된다.


Abstract Method로 선언된것은 Primitive Method 또는 Hook Method라고 한다.


하위 클래스에서 작성할 메서드이다.


5. Solution Code

 - AbstractClass

public abstract class AbstractClass {

public void templateMethod() {

logic1();
// Primitive Method -> 하위 클래스에서 각각 구현
// 이 메서드는 ConcreteClass1, 2에서 특수화(오버라이드) 됨
primitiveMethod();
logic2();
logic3();
}

// Primitive Method
protected abstract void primitiveMethod();

private void logic1() {
System.out.println("logic1()");
}

private void logic2() {
System.out.println("logic2()");
}

private void logic3() {
System.out.println("logic3()");
}
}

공통적인 로직을 수행하는 AbstractClass이다. primitiveMethod() (예제에서의 uniqueMethod())의 경우는 하위 클래스에서 구현해야 할 내용이므로 abstract method로 정의만하고 구현하지 않는다.


여기서, Primitive Method는 외부에서 참조할 수 없고, 자식 클래스는 참조할 수 있도록 접근제어자가 private에서 protected로 변경된다.


 - ConcreteClass1, 2

public class ConcreteClass1 extends AbstractClass {

// Primitive Method
protected void primitiveMethod() {
System.out.println("ConcreteClass1# Primitive Method()");
}
}

public class ConcreteClass2 extends AbstractClass {

// Primitive Method
protected void primitiveMethod() {
System.out.println("ConcreteClass2# Primitive Method()");
}
}

AbstractClass를 상속받는 ConcreteClass1, 2이다. 추상클래스인 AbstractClass에서 정의된 Abstract Method인 primitiveMethod()를 구현한다. 자신의 클래스 이름을 출력해야하므로 각자 구현해야한다.


 - Client

public class Client {
public static void main(String[] args) {
AbstractClass abstractClass1 = new ConcreteClass1();
AbstractClass abstractClass2 = new ConcreteClass2();

abstractClass1.templateMethod();
System.out.println();
abstractClass2.templateMethod();

}
}

출력 결과는 다음과 같다.


6. Template Method Pattern

전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할때 유용하다.

다른 관점에서 보면 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.


- UML & Sequence Diagram

 




7. 장단점


장점 : 같은 부모를 상속받는 하위 클래스끼리의 중복된 코드를 부모 클래스에서 구현함으로서 코드의 중복을 줄일 수 있고, 하위 클래스 각각이 다르게 행동해야 하는 메서드는 다형성으로 각각 구현함으로써 유지보수성을 높일 수 있다.


단점 : 초보자의 경우, 단순히 소스코드의 중복을 줄이기 위해 상속을 사용하는 경우 복잡도가 증가할 수 있다.

대표적으로, 부모 클래스에 존재하는 몇 개의 메서드를 쓰기만을 위해 상속하는 경우이다.


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

Chain Of Responsibility Pattern  (0) 2018.02.16
Iterator Pattern / Iterable Interface  (0) 2018.02.15
Observer Pattern  (0) 2017.09.26
Command Pattern  (0) 2017.09.26
State Pattern과 Strategy Pattern의 공통점과 차이점  (0) 2017.09.26
Comments