유키 히로시, JAVA 언어로 배우는 디자인 패턴 입문


항상 디자인 패턴이 중요하다는 말만 들어 보다가, 이제서야 위의 책으로 디자인 패턴을 공부해 보았습니다. 도서명이 '입문'인 만큼 디자인 패턴에 대해 전혀 지식이 없는 사람도 쉽게 접근할 수 있고, 책 내용도 알차 보였습니다. Java 문법에 어느 정도 익숙해졌다고 생각하는 초급자에게 추천해 주고 싶은 책입니다. 번역도 깔끔하다고 생각 합니다.


아래는 책을 통해 공부한 23가지 디자인 패턴을 나름대로 요약 한 내용입니다. 이후에 디자인 패턴을 활용하고자 할 때 참조할 목적으로 정리하였습니다. 다만 나중에 디자인 패턴을 사용하게 될 때에는 원저 GOF, Design Patterns: Elements of Reusable Object-Oriented Software 를 보며 조금 더 깊이 있게 해당 패턴을 공부할 생각입니다.


아래에 나열한 디자인 패턴의 분류와 순서는 "유키 히로시, Java 언어로 배우는 디자인 패턴 입문" 의 도서에 소개된 것을 따르므로 "GOF, Design Patterns" 의 그것과는 차이가 있습니다.


Part 1 디자인 패턴과 친해지기

Chapter 01 Iterator - 순서대로 지정해서 처리하기

* 목적 - 지정된 순서대로 list 를 순회한다.

* 구성 - Iterator 는 지정된 순서 대로 Element 로 이루어진 list 를 순회한다.

* 참고 - 한 번에 여러 개의 Iterator 을 만들 수 있다.

         - Iterator 는 정방향 순회, 역방향 순회, 점프 등을 할 수 있다.

         - Iterator 는 list 를 순회할 뿐이다. 순회와 동시에 반복작업을 수행하는 것은 Visitor 패턴이다.

Chapter 02 Adapter - 바꿔서 재이용하기

* 목적 - 이미 존재하는 클래스를 이용하여 새로운 API 를 제공한다.

            ex) 기존에 만든 클레스를 재사용하거나, 버전 업을 할 때

* 구성 - Adapter 는 Adaptee 를 상속하거나 이용(has)하여 새로운 API(methods) 를 제공한다.

Part 2 하위 클래스에게 위임하기

Chapter 03 Template Method - 하위 클래스에서 구체적으로 처리하기

* 목적 - 비슷한 기능을 하는 클래스들을 하나의 SuperClass 로 묶어서 logic 을 공통화한다.

* 구성 - abstract SuperClass 는 외부에서 이용할 template methods 를 정의해 두고, SubClass 에서 methods 를 실제로 구현한다.

Chapter 04 Factory Method - 하위 클래스에서 인스턴스 만들기

* 목적 - 인스턴스를 생성하는 공장 역할인 ConcreteFactory 를 여러 개 만들어, 공통적인 작업을 수행한다.

* 구성 - framework 패키지에는 abstract Factory, abstract Product 클래스를 정의해 두고,

            ConcreteFactory, ConcreteProduct 클래스에서 이를 overriding 하여 구체적으로 구현한다.

* 참고 - Factory Method 패턴은 Template Method 패턴의 전형적인 응용이다.

         - Factory Method 패턴은 method 를 overriding 하는 것이고(클래스에 create 기능이 있는 것이다. 클래스가 Factory 객체일 필요는 없다), Abstract Factory 패턴은 클래스를 상속하는 것이다(클래스가 Factory 객체이다).

Part 3 인스턴스 만들기

Chapter 05 Singleton - 인스턴스를 한 개만 만들기

* 목적 - 클래스가 단 하나의 인스턴스만을 갖도록 한다.

* 구성 - 생성자를 private 으로 지정하여 외부에서 인스턴스를 생성하는 것을 차단한다.

* 참고 - Singleton 패턴은 멀티쓰레드 환경에서 제대로 동작하도록 만드는 것이 중요하다.

Chapter 06 Prototype - 복사해서 인스턴스 만들기

* 목적 - 클래스명을 직접 사용하지 않고 인스턴스를 생성하고자 할 때 사용한다.

            ① 일일이 클래스를 만들기에는 종류가 너무 다양할 때

            ② 인스턴스 생성시 사용자로부터 정보를 입력받는 등의 복잡한 과정이 수반될 때

            ③ 인스턴스를 이용하는 Client 클래스가 인스턴스의 클래스를 모를 때 ex) 패키지가 다를 때

* 구성 - Object.clone() 메스드를 이용하여, prototype 인 "인스턴스"(클래스가 아님)를 복사하여 새로운 인스턴스를 생성한다.

* 참고 - Java 에서는 소스 파일(.java)이 없이 클래스 파일(.class)만 있어도 클래스를 재이용 할 수 있도록 하는 것이 중요하다. 소스 내부에 이용할 클래스명이 명시되어 있으면 클래스들을 분리해서 재이용 하기가 힘들어 진다.

         - clone() 메소드를 호출하려면 Cloneable 인터페이스(marker interface)를 구현해야 한다.

         - clone() 메소드는 deep copy 가 아닌 shallow copy 를 수행한다.

Chapter 07 Builder - 복잡한 인스턴스 조립하기

* 목적 - 인스턴스들을 조립하여 하나의 복잡한 구조를 만든다.

* 구성 - abstract Builder 가 복잡한 인스턴스를 생성하는 과정은 Builder 을 호출하는 Director 클래스에 의해 감추어 진다. 외부에 노출되는 Director 클래스는 단순한 메소드만을 가진다.Builder 클래스의 구체적인 기능은 ConcreteBuilder 클래스에 의해 구현된다.

Chapter 08 Abstarct Factory - 관련 부품을 조합해서 제품 만들기

* 목적 - 여러 가지 제품을 생성, 조립하는 비슷한 공장들을 하나의 SuperClass 로 묶는다.

* 참고 - ConcreteFactory 를 하나 더 추가하는 것은 간단하지만, ConcreteProduct 를 하나 더 추가하는 것은 복잡하다.

         - Java 에서 인스턴스를 생성하는 세 가지 방법

            ① new 키워드

            ② Object.clone()

            ③ Class.newInstance()

         - Factory Method 패턴은 method 를 overriding 하는 것이고(클래스에 create 기능이 있는 것이다. 클래스가 Factory 객체일 필요는 없다), Abstract Factory 패턴은 클래스를 상속하는 것이다(클래스가 Factory 객체이다).

Part 4 분리해서 생각하기

Chapter 09 Bridge - 기능 계층과 구현 계층 분리하기

* 목적 - 하나의 클래스 계층 안에서 두 역할이 혼재되지 않도록 기능 추가의 클래스 계층과 기능 구현의 클래스 계층을 분리한다. 패턴 이름은 Bridge 이지만, 실제로는 Bridge 로 두 계층을 연결하는 것 보다는 두 계층을 적절히 분리하는 것이 핵심이다. 여기서 Bridge 는 두 클래스 계층 간의 위임 관계를 말한다. 상속 관계가 아닌 위임 관계이므로 클래스의 연결이 약해져 이후 수정·확장이 쉬워진다.

* 구성 - SubClassA 는 SuperClassA 에 새로운 기능을 추가한다. (A : 기본적 기능이 완성된 클래스)

            SubClassB 는 SuperClassB 에서 정의된 기능을 구체적으로 구현한다. (B : 추상클래스 또는 인터페이스)

* 참고 - 클래스 계층은 두 가지 역할을 할 수 있다.

            ① 기능 추가(상속을 통한 클래스의 확장)

            ② 기능 구현(추상 메소드의 overriding 을 통한 클래스 구체화)

Chapter 10 Strategy - 알고리즘을 모두 바꾸기

* 목적 - 하나의 클래스에서 여러 가지 알고리즘을 동적으로 교체하며 사용한다.

* 구성 - 알고리즘을 클래스 내부에서 사용하지 않고, 별도의 클래스를 만들어 그 안에 구현한다.

Part 5 동일시하기

Chapter 11 Composite - 그릇과 내용물을 동일시하기

* 목적 - component 를 container 처럼 다루어 component 안에 또다른 component 를 담는다.(재귀적 구조)

            ex) GUI 의 awt.Panel : Panel 은 component 이면서도 다른 component 를 담을 수 있다.

                 Android 의 ViewGroup : ViewGroup 은 View 를 상속하면서도 View 를 담을 수 있다.

                 디렉토리 구조(Tree)의 폴더 : 폴더는 상위 폴더의 내용물이면서도 다른 파일, 폴더를 담을 수 있다.

* 구성 - LeafClass, CompositeClass 는 ComponentClass 를 상속한다.

            compositeClass 는 ComponentClass 로 이루어진 list 를 갖고 있다.(재귀적 구조)

* 참고 - Composite 패턴은 재귀적으로 '내용물을 포함' 하는 것이고, Decorator 패턴은 재귀적으로 '기능을 추가' 하는 것이다.

Chapter 12 Decorator - 장식과 내용물을 동일시하기

* 목적 - decorator 를 component 처럼 다루어 decorator 를 또다시 꾸며준다.

            ex) 아래 예에서 b 는 c 에게는 decorator 이지만 a 에게는 component 가 된다.

                 a a a a a

                 a b b b a

                 a b c b a

                 a b b b a

                 a a a a a

* 구성 - DecoratorClass 는 ComponentClass 를 상속하는 동시에 componentClass 로 이루어진 list 를 갖는다.(재귀적 구조)

* 참고 - 투과적 인터페이스(API) : 위의 예에서, b는 c를 감싸고 있지만 c의 인터페이스(API)를 감추지는 않는다.

         - Decorator 패턴은 매우 유사한 클래스를 많이 만들게 된다는 단점이 있다.

         - java.io, javax.swing.border 패키지 등에서 Decorator 패턴을 사용한다.

         - Composite 패턴은 재귀적으로 '내용물을 포함' 하는 것이고, Decorator 패턴은 재귀적으로 '기능을 추가' 하는 것이다.

Part 6 구조를 돌아다니기

Chapter 13 Visitor - 데이터 구조를 돌아다니면서 처리하기

* 목적 - 데이터 구조 안의 여러 요소들을 탐색(방문)하며 특정 작업을 수행할 때, '처리 과정'을 '데이터 구조'에서 분리시킨다.

* 구성 - double dispatch : 한 쌍의 methods 로 인해 실제 기능이 수행됨.

            데이터 구조 내의 ElementClass 의 accept(visitor) : visitor 이 방문했을 때의 반응

            VisitorClass 의 visit(element) : element 를 방문했을 때의 작업

* 참고 - ConcreteVisitor 를 추가하는 것은 간단하지만, ConcreteElement 를 추가하는 것은 복잡하다.

            (모든 ConcreteVisitor 를 수정하여 새로운 element 를 방문했을 때의 작업을 추가해야 함)

         - Visitor 는 최소한의 정보만 가지고 작업을 수행하도록 한다. element 의 정보에 너무 의존하면 추후 수정이 힘들다.

Chapter 14 Chain of Responsibility - 책임 떠넘기기

* 목적 - A ⊂ B ⊂ C 의 구조일 때, 사건 x 를 A, B, C 순으로 전달하며 누가 처리할 지 결정한다.

            ex) event bubbling, 예외 처리

* 구성 - 사건 x 는 '누가 어떻게 자신을 처리할 지'를 알지 못한다.

            책임사슬 내에서 A → B, B → C 로 사건 x 를 전달하다가 처리하게 된다. (요청자와 처리자의 분리)

* 참고 - chaining 시 속도 저하가 발생할 수 있다. 처리 속도가 상당히 중요할 때에는 이 패턴을 사용하지 않는 것이 좋다.

Part 7 단순화하기

Chapter 15 Facade - 단순한 창구

* 목적 - 여러 개의 클래스들이 복잡하게 얽혀 있을 때, 클래스의 전체 관계를 지휘하는 클래스를 만들어 외부로 노출할 인터페이스(API)를 단순화한다. 하나의 패키지 내부에서 복잡한 상호작용이 일어나 외부에서 이를 이용하기가 쉽지 않을 때 사용한다.

* 참고 - 여러 Facade 들의 역할을 다시 통합하는 Facade 를 재귀적으로 만들 수도 있다.

Chapter 16 Mediator - 중개인을 통해서 처리하기

* 목적 - 여러 클래스들이 복잡한 상호작용을 통해 기능할 때, 상호작용이 하나의 중개인을 통해 일어나도록 조정한다. (logic 을 집중시켜 수정·디버깅 을 용이하게 한다)

* 참고 - ConcreteColleague 는 재이용 할 수 있지만,

            ConcreteMediator 는 어플리케이션에 대한 의존도가 높아 재이용성이 낮다.

         - GUI component 들이 상호간에 영향을 미칠 때는 Mediator 패턴을 이용하는 것이 효과적이다.

         - Facade 는 외부에 API 를 제공하고(단방향), Mediator 는 상호간의 역할을 조정한다(쌍방향).

Part 8 상태를 관리하기

Chapter 17 Observer - 상태의 변화를 알려주기

* 목적 - 상태에 변화가 생겼을 때 이를 즉각적으로 전달받아 프로그램에 상대변화를 반영한다.

* 참고 - 두 개의 observer 가 서로 상대변화 event 를 일으킬 때에는 무한 loop 가 발생할 수 있으므로 flag 를 설정한다.

         - Observer 패턴은 실제로는 능동적으로 '관찰'하는 것이 아니라, 상태변화를 '전달받는' 입장이므로

            Publish-Subscribe 패턴이라고도 한다.

         - MVC(Model View Controller) : Model 은 Subject(Publisher), View 는 Observer(Subscriber) 에 대응된다.

         - Mediator 패턴에서는 Mediator 하나가 colleague 여럿의 역할을 조정한다.

           Observer 패턴에서는 Observer 여럿이 Subject 하나의 상태 변화를 반영하는 작업(동기화)을 수행한다.

         - Observer 패턴이 event listener 라면 Command 패턴은 event handler 이다.

Chapter 18 Memento - 상태를 저장하기

* 목적 - 인스턴스의 상태를 저장해 둔다.

            ex) history(작업 내역) 저장, undo·redo 실행

* 구성 - MementoClass 는 캡슐화 파괴를 막기 위해 두 종류의 인터페이스(API)를 가진다.

            ① wide interface → MementoClass 가 저장할 인스턴스인 Originator 가 이용하는 인터페이스.

                Originator 는 memento 를 생성해야 하므로 memento 의 상태를 폭넓게 조정한다.

            ② narrow interface  → MementoClass 를 이용하는 Client 가 이용하는 인터페이스.

                Client 는 캡슐화를 위해 memento 의 인터페이스 중 필요한 부분만을 이용한다.

* 참고 - Client 는 '언제' memento 를 생성하고, Originator 의 상태를 memento 시점으로 복원할 지를 결정한다.

            Originator 는 '어떻게' memento 를 생성하고, memento 의 시점으로 복원될 지를 결정한다.

Chapter 19 State - 상태를 클래스로 표현하기

* 목적 - A, B, C 세 가지 상황에 따라 처리 방법이 다를 때, 이를 if-else 문으로 구분하지 않고 각 상황을 나타내는

            세 개의 클래스로 구분한다. (switch 문을 인스턴스로 대체한다)

* 구성 - ContextClass 가 여러 StateClass 들을 담고 있다가, 상황에 맞는 StateClass 를 현재의 state 로 설정해

            사건을 처리하게 한다.

* 참고 - 새로운 state 를 추가하는 것은 간단하지만, 각 state 들에 새로운 기능을 추가하는 것은 복잡하다.

         - 상태 전환은 ContextClass 가 할 수도 있고, StateClass 가 할 수도 있다.

            ① ContextClass 가 상태전환 → StateClass 간의 의존관계는 약해지고

                                                         ContextClass 와 StateClass 간의 의존관계는 강해짐

            ② StateClass 가 상태전환 → StateClass 간의 의존관계는 강해지고

                                                     ContextClass 와 StateClass 간의 의존관계는 약해짐

Part 9 낭비 없애기

Chapter 20 Flyweight - 동일한 것을 공유해서 낭비 없애기

* 목적 - 같은 종류의 인스턴스가 많이 필요할 경우, 이를 가능한 공유시켜서 메모리 사용을 최소화한다.

* 구성 - 공유할 인스턴스들을 HashMap 등으로 이루어진 pool 에 담아 둔다. 이후 사용하려는 인스턴스가 pool 에 있을 경우 이미 만들어진 인스턴스를 그대로 사용하고, pool 에 없을 경우 인스턴스를 새로 만든다.

* 참고 - intrinsic information : 공유 가능(FlyweightClass 들을 이용하는 Client 들이 공통적으로 가지고 있는 특성)

            extrinsic information : 공유 불가(각 Client 들이 개별적으로 가지고 있는 특성)

         - Flyweight 패턴은 메모리 사용 뿐 아니라, 인스턴스 생성 시간을 단축하는 효과도 있다.

         - OS 에 따라서는 파일 핸들, 윈도우 패널 수에 제한을 두기도 한다. 이때에는 인스턴스를 공유할 수 밖에 없다.

         - pool 에 있는 인스턴스는 자동으로 garbage collecting 되지 않는다.

Chapter 21 Proxy - 필요해지면 만들기

* 목적 - (메모리·생성시간 등의 이유로)인스턴스 생성이 부담될 때, 간단한 작업은 대리인에게 위임하여 해당 인스턴스의 생성을 최대한 자제한다.

            ex) HTTP Proxy 의 web page cashing

* 구성 - ProxyClass 는 간단한 업무는 자신이 하다가 꼭 SubjectClass 가 필요한 경우에만 SubjectClass 의 인스턴스를 생성하여 업무를 전달한다. 이 때 SubjectClass 는 해당 업무가 ProxyClass 의 요청인지, ClientClass 의 요청인지 알지 못한다. ClientClass 는 ProxyClass 와 SubjectClass 가 구현하는 인터페이스(API)를 통해 업무를 요청한다.

* 참고 - Proxy 의 종류에는 Virtual Proxy, Remote Proxy, Access Proxy 세 가지가 있다.

         - Adater : Adaptee 의 API ≠ Adapter 의 API

           Proxy : Subject 의 API = Proxy 의 API (투과적 인터페이스)

         - Decorator 패턴의 목적 : 새로운 기능 추가

           Proxy 패턴의 목적 : SubjectClass 에 대한 Access 감소

Part 10 클래스로 표현하기

Chapter 22 Command - 명령을 클래스로 하기

* 목적 - 수행할 일을 작업 단위로 묶어 인스턴스로 만든다. 같은 작업을 반복하거나(Macro, event 처리 등) 작업 List(history, array 등)를 만들고자 할 때 사용한다.

* 참고 - Observer 패턴이 event listener 라면 Command 패턴은 event handler 이다.

Chapter 23 Interpreter - 문법규칙을 클래스로 표현하기

* 목적 - Java 언어 상에서 새로운 interpreter(해석기)를 구현한다.

           ex) 정규표현식, 일괄처리언어(batch language)

* 구성 - ① ContextClass : 명령어 분리기. StringTokenizer 등으로 명령어들을 분리한다.

            ② abstract ExpressionClass : 명령어. 명령어를 해석하는 abstract method parse() 를 가진다.

                └ TerminalExpressionClass : 하위 노드를 갖지 않는, 문법 규칙의 종착점인 Expression

                └ NonterminalExpressionClass : 하위 노드를 갖는 Expression. 제어문(if, while), 선언부(class, void) 등.

         - Expression 들을 포함 관계에 따라 트리로 구축한 후, 상위 노드부터 parsing 해 나가며 구문을 분석한다.

마치며...

 GOF 의 업적은 세상에 없던 새로운 방법들을 만들어 낸 것이 아니라, 많은 사람들이 사용하는 방법들을 정리한 것이라 합니다. 선배들의 고민과 노하우를 추상화하여 패턴으로 만들어 낸 것입니다. 그래서인지 책을 읽으면서 기발한 방법들을 배운다는 느낌 보다는 여러 방법들을 체계적으로 정리한다는 느낌이 들었습니다.

 저는 디자인 패턴을 배운 적이 없지만, 위의 23 가지 디자인 패턴 중에는 이미 내가 사용하고 있던 방법들도 많았습니다. 그러나 이제까지는 같은 패턴으로 볼 수 있는 문제들도 큰 연관성을 찾지 못한 채 따로 고민하며 해결해 왔습니다. 문제를 추상화시키고 정리하는 과정이 없었기 때문입니다. 또한 내가 생각한 해결 방법을 다른 사람에게 설명하기 위해서도 많은 시간을 들여야 했습니다. 내 생각을 하나의 용어로 정리해서 표현할 방법을 몰랐기 때문입니다.

 앞으로 프로그래밍을 통해 어떤 문제를 해결할 때에는 해당 문제에 적용할 수 있는 디자인 패턴이 무엇인지 고민해 보고, 그 디자인 패턴으로 해결해 왔던 문제들을 해당 문제와 연관지어 생각해 봐야겠습니다. 디자인 패턴의 용어들과 개념을 적극적으로 활용하다 보면, 언젠가는 진정으로 '객체지향적'인 프로그래밍을 할 수 있을 것이라 믿습니다.


'독서' 카테고리의 다른 글

네트워크 개론  (0) 2016.05.02
도난 당한 패스워드  (4) 2014.11.30
거꾸로 배우는 소프트웨어 개발  (0) 2014.11.15
폴리글랏 프로그래밍  (0) 2014.11.15
어떻게 원하는 것을 얻는가  (0) 2014.10.25