JAVA 언어로 배우는 디자인 패턴 입문, 헤드퍼스트 디자인 패턴을 통해 학습 중이며 두 책의 예제 위주로 정리.
생성패턴(Creational Pattern)
1. 싱글톤(Singleton)
2. 빌더(Builder)
3. 팩토리 메소드(Factory Method)
4. 추상 팩토리(Abstract Factory)
5. 프로토타입(Prototype)
구조패턴(Structural Pattern)
2. 브릿지(Bridge)
3. 컴포지트(Composite)
4. 데코레이터(Decorator)
5. 퍼사드(Facade)
6. 플라이웨이트(flyweight)
7. 프록시(Proxy)
행동(행위) 패턴(Behavioral Pattern)
1. 책임 연쇄(Chain of Responsibility)
2. 커맨드(Command)
3. 인터프리터(Interpreter)
5. 중재자(Mediator)
6. 메멘토(Memento)
7. 옵저버(Observer)
8. 상태(State)
10. 템플릿 메소드(Template Method)
11. 방문자(Visitor)
템플릿 메소드 패턴은 행동(행위) 패턴에 속하는 디자인 패턴으로 상위 클래스에서 처리의 뼈대를 결정하고 하위 클래스에서 구체적인 내용을 결정하는 패턴이다.
알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다.
예제
문자 또는 문자열을 출력하는 프로그램.
Character 타입의 경우 << >> 안에 해당 문자가 5번 반복이 되며, String 타입의 경우 +---+ +---+ 사이에 | 문자열 | 형태로 개행이 되며 5번 반복이 된다.
구조로는 추상클래스인 AbstractDisplay 에는 open, print, close라는 세개의 추상 메소드, open, 반복문으로 5번 수행하는 print, close 순서로 호출하고 있는 display 메소드가 존재한다.
구현체인 CharDisplay와 StringDisplay는 각각 출력할 문자 혹은 문자열 필드를 갖고 있으며, AbstractDisplay를 상속받아 추상메소드를 구현하고 있다.
이 구조에서 Template Method는 display가 된다.
//예제 출처 - JAVA 언어로 배우는 디자인 패턴 입문
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();
}
}
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
@Override
public void open() {
System.out.println("<<");
}
@Override
public void print() {
System.out.print(ch);
}
@Override
public void close() {
System.out.println(">>");
}
}
public class StringDisplay extends AbstractDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.length();
}
@Overrdie
public void open() {
printLine();
}
@Overrdie
public void print() {
System.out.println("|" + string + "|");
}
@Override
public void close() {
printLine();
}
private void printLine() {
System.out.print("+");
for(int i = 0; i < width; i++)
System.out.print("-");
System.out.println("+");
}
}
public class Main {
public static void main(String[] args) {
AbstractDisplay charH = new CharDisplay('H');
AbstractDisplay stringHello = new StringDisplay("Hello World");
charH.display();
stringHello.display();
}
}
실행결과
<<HHHHH>>
+----------+
|Hello World|
|Hello World|
|Hello World|
|Hello World|
|Hello World|
+----------+
예제 작성 전에 Template Method는 display() 메소드라고 했다.
display() 메소드는 동일하게 open() -> print() * 5 -> close() 를 처리하고 있다.
즉, 처리과정에 대한 뼈대가 된다.
그럼 상위 클래스인 AbstractDisplay에서 뼈대를 결정한 것이라고 볼 수 있다.
그리고 하위 클래스인 CharDisplay와 StringDisplay에서는 AbstractDisplay를 상속받아 메소드를 구현하고 있다.
각 클래스에서는 open(), print(), close()를 어떻게 처리할지 구체적인 구현을 담당하고 있다.
처음 템플릿 메소드의 설명에 빗대어 본다면,
상위 클래스(AbstractDisplay)에서 처리의 뼈대(display())를 결정하고 하위 클래스(CharDisplay, StringDisplay)에서 그 구체적인 내용(open(), print(), close())을 결정하는 패턴이다.
코드를 보며 재정리.
AbstractDisplay에는 구현해야 할 추상메소드(open(), print(), close())와 템플릿 메소드(display())가 존재한다.
템플릿 메소드는 추상 메소드들을 호출하게 된다.
실제 사용한다고 하면 여러 알고리즘 + 추상 메소드의 구조가 될 것이다.
즉, 템플릿 메소드는 해당 처리에 대한 정형화된 틀이 된다.
그리고 이 틀을 만들어 두게 됨으로써 다른 타입을 받거나 다른 결과를 내야 하는 경우 중복되는 처리 과정을 거치는 여러 메소드들을 생성하지 않고 재사용할 수 있다.
즉, CharDisplay와 StringDisplay의 공통점으로는 open() -> print() * 5 -> close()로 처리되고 있는데,
이걸 각 클래스의 display() 메소드로 처리한다고 한다면 동일한 코드가 반복되거나 혹은 전혀 분리되어 있지 않은 코드가 생길것이다.
public class CharDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
public void display() {
System.out.print("<<");
for(int i = 0; i < 5; i++)
System.out.print(ch);
System.out.println(">>");
}
}
public class StringDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.length();
}
private void open() {
printLine();
}
private void print() {
for(int i = 0; i < 5; i++)
System.out.println("|" + string + "|");
}
private void close() {
printLine();
}
public void display() {
open();
print();
close();
}
private void printLine() {
System.out.print("+");
for(int i = 0; i < width; i++)
System.out.print("-");
System.out.println("+");
}
}
이러한 코드 구조라면 틀만 봤을때는 같은 처리과정을 갖고 있는데, 각 클래스에서 구현하게 됨으로써 중복이 발생한다.
그리고 만약 이 문자 또는 문자열을 10번 출력하도록 수정해야 한다면 모든 클래스를 체크하면서 5번을 10으로 수정해줘야 한다.
또한 재사용성이라고는 전혀 찾아볼 수도 없다.
그래서 이 처리 과정들을 추상메소드로 구체적인 것은 하위 클래스에서 맡겨놓고 처리하는 틀은 템플릿 메소드로 처리하게 되면 코드도 간결해져 코드가 보다 간결해 질 수 있고, 틀을 수정해야 하더라도 각 하위 클래스에서 수정할 것은 없고 템플릿 메소드에서만 수정이 발생하게 된다.
이렇게 상위 클래스 타입의 변수에 생성한 인스턴스 중 어느것을 대입하더라도 제대로 동작할 수 있도록 하는 원칙을 리스코프 치환법칙(LSP, Liskov Substitution Principle)이라고 한다.
템플릿 메소드 패턴을 하위 클래스 관점에서 본다면
- 상위 클래스에서 정의 된 메소드를 하위 클래스에서 이용할 수 있다.
- 하위 클래스에서 약간의 메소드를 기술하는 것만으로 새로운 기능을 추가할 수 있다.
- 하위 클래스에서 메소드를 오버라이드 하게 되면 동작을 변경할 수 있다.
상위 클래스 관점에서 본다면
- 하위 클래스에서 그 메소드를 구현하기를 기대한다.
- 하위 클래스에 메소드 구현을 요청한다.
즉, 하위 클래스에서는 상위 클래스에서 선언한 추상 메소드를 구현할 책임이 있다고 할 수 있는데 이것을 subclass responsibility(하위 클래스의 책임)이라고 한다.
'JAVA > Design Pattern' 카테고리의 다른 글
디자인패턴(Java) - 전략패턴(Strategy Pattern) (1) | 2023.12.19 |
---|---|
디자인패턴(Java) - 어댑터(Adapter) (0) | 2023.12.16 |
디자인패턴(Java) - 반복자(Iterator) (1) | 2023.12.15 |
디자인패턴(Design Pattern)이란 (0) | 2023.12.15 |