Search

Design Pattern

태그
CS
Head-First-Design-Patterns-master.zip
354.0KB

스트래티지 패턴

디자인 원칙

1.
애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.
2.
구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.

동기

한 행동을 바꿀 때마다 그 행동이 정의되어있는 서로 다른 서브클래스들을 전부 찾아서 코드를 일일히 고쳐야하고, 그 과정에서 버그가 생길 수도 있다.
→ 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 '캡슐화' 함.
→ 코드를 변경하는 과정에서 의도하지 않는 일이 일어나는 것을 줄이면서 시스템의 유연성은 향상시킨다.

다이어그램

특정 객체에 대한 행동을 캡슐화하여 인터페이스로 정의하고
해당 인터페이스를 구현하는 ConcreteClass를 만들어서,
Setter 메소드로 Car에서 끼워넣을 수 있도록 한다.
알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
이를 사용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

행동에 대해 여러 가지 알고리즘을 캡슐화하고, 동적으로 교체할 수 있다.

옵저버 패턴

동기

관찰하고 싶은 객체(Observable)의 상태에 변화가 있을 때마다, 옵저버들에게 메시지를 전달하고 싶을 때, 사용한다.

디자인 원칙

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야한다.

다이어그램

주제를 나타내는 Subject 인터페이스. 객체에서 옵저버로 등록하거나 옵저버 목록에서 탈퇴하고 싶을 때는 이 인터페이스에 있는 메소드를 사용
주제 역할을 하는 ConcreteClass 에서는 Subject 인터페이스를 구현해야한다.
옵저버 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될 수 있다.
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.
두 가지 방식이 있다. 푸쉬와 풀
푸쉬방식을 사용할 때는 데이터를 notifyObservers(arg) 메소드의 인자로 전달하는 데이터 객체 형태로 전달해야합니다.
풀 방식을 사용할 때는 옵저버 쪽에서 전달받은 Observable 객체로부터 원하는 데이터를 가져가는 방식을 써야한다. 대체로 풀 방식이 더 "옳은" 것으로 간주된다.

데코레이터 패턴

동기

상속을 해서 기능을 추가하려고 하면
1.
첨가물의 가격이 바뀔때마다 기존 코드를 수정해야하고
2.
첨가물 종류가 많아지면 새로운 메소드를 추가해야하고, 수퍼 클래스의 cost()메소드도 고쳐야함.
3.
새로운 음료가 출시될 수도 있는데, 상속받는 메소드가 그대로 들어간다.
→ 따라서 구성과 위임을 통해서 실행중에 행동을 '상속'시키는 방법을 써야한다.

디자인 원칙

OCP : 클래스는 확장에 대해서는 열려있고, 코드 변경에 대해서는 닫혀있어야한다.
클래스를 확장하고 원하는 행동을 마음대로 추가할 수 있어야한다.
버그를 박멸하고 코드를 고치느라 정말 힘들었다. 더 이상의 변경은 받아들일 수 없다.

다이어그램

한 객체를 여러 데코레이터로 감쌀 수 있다.
원래 객체가 들어가는 자리에 자기자신을 넣어도 된다. 자신이 장식하는 객체에게 어떤 행동을 위임하는 것외 추가적인 작업을 수행.
언제든 감쌀 수 있기 때문에 실행 중에 데코레이터를 마음대로 적용가능하다.
일반적으로 새로운 메소드를 추가하는 대신 Component에 원래 있던 메소드를 호출하기 전, 또는 후에 별도의 작업을 처리하는 방식으로 새로운 기능을 더한다.
자잘한 클래스들이 추가되곤 한다.
클라이언트 객체는 데코레이터를 쓴다는 걸 모른다.
구성요소 초기화하기 힘들다. 따라서 팩토리 또는 빌더와 함께 쓰자.

객체의 행동의 수와 순서를 캡슐화한다.

팩토리 패턴

동기

Duck duck; if(picnic){ duck = new MallardDuck(); } else if(hunting){ duck = new DecoyDuck(); }else if(inBathTub){ duck = new RubberDuck(); }
Java
복사
만들어지는 인스턴스의 형식은 실행 시에 주어진 조건에 따라 결정된다.
이런 코드는, 변경하거나 확장해야할 때, 코드를 다시 확인하고 추가 또는 제거해야한다는 것을 뜻한다. 코드를 이런 식으로 만들면 관리 및 갱신하기 어려워지고, 오류가 생길 가능성도 높아지게 된다.
간단한 피자 팩토리 → 기존에 있는 생성 부분을 전담할 객체 클래스를 정의한다.

다이어그램

정의

객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다. 팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡긴다.

디자인 원칙

추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않아야한ㄷ,
'고수준' 구성요소는 다른 '저수준' 구성요소에 의해 정의되는 행동이 들어있는 구성요소를 의미한다.
PizzaStore의 행동은 피자에 의해 정의되기 때문에, PizzaStore는 고수준. 피자 객체들은 저수준 구성요소
원래는 고수준 구성요소가 저수준 구성요소에 전부 의존하지만, 의존성 뒤집기 원칙에 의하면 고수준 구성요소인 PizzaStore와 저수준 구성요소인 피자 객체들이 모두 추상 클래스인 Pizza에 의존한다.
의존성이 위에서 아래로 내래가기만 했던 것과는 반대로 뒤집어진 거를 의미한다.

추상 팩토리 패턴

동기

팩토리 메서드 패턴을 통해 여러 가지 제품군을 생성할 수 있는 '팩토리'를 만드는 데까지 성공
그럼 '여러 가지 제품군' 그 종류 자체를 여러 가지로 만들어내기 위한 팩토리는? → 추상 팩토리 패턴이다.
추상 팩토리를 통해 제품군을 생성하기 위한 인터페이스를 생성할 수 있다.
즉, 추상 팩토리 패턴을 사용하면, 여러 가지의 제품군을 구상하기 위한 재료들을 인터페이스로 삼아놓고, 해당 제품군을 만드는 팩토리를 구현해낼 수 있다.
즉 정리하자면
팩토리 메서드
서로 다른 제품을 만들 때, 서브 클래스에서 제품 생성에 대한 코드를 구현한다.
서브 클래스에 제품 생성을 위임.
추상 팩토리 메서드
제품을 만드는데 들어가는 다양한 제품군을 위한 인터페이스를 제공하고, 이를 통해 제품을 완성시키거나, 제품군을 탄생시킨다.

싱글턴 패턴

동기

객체 중에는 사실 하나만 있으면 되는게 많다. 스레드 풀이라든가, 캐시, 대화상자, 사용자 설정이라든가, 레지스트리를 설치하는 객체, 로그 기록용 객체 등등
전역 변수에 객체를 대입하면 애플리케이션이 시작될 때 객체가 생성된다. 근데 그 객체가 자원을 많이 차지하고, 애플리케이션을 쓸 때까지 만약 그 객체를 한 번도 쓰지 않는다면, 아무 쓸데가 없다.
필요할 때 객체를 쓸 수 있다.

구현법

public class Singleton{ private static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } }
Java
복사

커맨드 패턴

동기

메소드 호출을 캡슐화할 수 있을까?
1.
제품이 추가될 때, 또 다른 메소드가 추가될 수 있다.
2.
리모컨 버튼을 누르면 자동으로 해야할 일을 처리할 수 있도록 만들어야한다. 그러면서도 리모컨 자체에서는 각 기능들의 구현 방법에 대해서 모르도록 해야한다.
→ 어떤 작업을 요청한 쪽하고 그 작업을 처리한 쪽을 분리시켜야한다.
→ 디자인에 커맨드 객체라는 거를 만들어서, 특정 객체에 대한 요청을 캡슐화시켜준다.
→ 각 버튼마다 커맨드 객체를 저장해두면 사용자가 버튼을 눌렀을 때, 커맨드 객체를 통해 작업을 처리하도록 만든다. 리모컨에서는 전혀 자세한 내용을 몰라도 된다.

다이어그램

클라이언트는 ConcreteCommand를 생성하고, 구성으로 들어갈 Recevier를 집어넣는다.
Invoker에는 Command객체를 세팅할 수 있는 함수가 있다.
ConcreteCommand에서 execute를 호출하면 Receiver의 Action들이 실행되도록 구현한다.

정의

커맨드 패턴을 이용하면 요구 사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수 있으며, 작업 취소 기능도 지원가능하다.
여기서 매개변수를 이용해서 요구사항을 집어넣을 수 있다는 뜻은, 여러 가지 커맨드 객체를 가질 수 있다는 것이다.
뿐만 아니라 커맨드 객체에 들어가는 execute와 비슷한 메소드를 추가적으로 생성할 수도 있을 것. → 이 점을 이용해서 로그를 기록하거나 요청 내역을 큐에 저장할 수도 있다는 것이다.
뿐만 아니라 매크로 커맨드를 만들어서 여러 커맨드에 저장되어있는 execute를 순차적으로 실행시킬 수도 있을 것이다.

어댑터 패턴

동기

어떤 소프트웨어 시스템이 있는데, 새로운 업체에서 제공한 클래스 라이브러리를 사용해야한다고 해보자. 근데 새로 채택한 업체에서 사용하는 인터페이스가 기존 업체에서 사용하던 인터페이스하고 다르다고 가정해보자.
이 때 어댑터를 사용해서, 제공한 클래스를 한 번 더 감싼다. 그렇게 해서 인터페이스를 통일 시키는 것
→ 이 때 구성을 이용해서 adaptee를 감싼다.

다이어그램

퍼사드 패턴

인터페이스를 단순화시키기 위해서 인터페이스를 변경하고자 한다. 하나 이상의 클래스의 복잡한 인터페이스를 깔끔한 퍼사드(겉모양)으로 덮어준다.

동기

이런 식의 상황일 때, 홈씨어터를 키려면, 별의별 짓을 다해야한다.
따라서 퍼사드 패턴을 써서, 홈씨어터에 대한 인터페이스를 재정의한다.
→ 이런 식으로

정의

어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다. 퍼사드에서 고수준의 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있다.

디자인 원칙

최소 지식 원칙
어던 객체든 그 객체와 상호작용을 하는 클래스의 개수에 주의해야하며, 그런 객체들과 어떻게 상호작용하는지에도 주의를 기울여야한다.

템플릿 메소드 패턴

알고리즘을 캡슐화해서, 서브클래스에서 언제든 필요할 때마다 가져다가 쓸 수 있도록 해보겠습니다.

동기

비슷한 알고리즘을 가지는 두 객체에게 공통적인 추상클래스를 부여하고, 이를 템플릿으로 가져다가 쓰는 방법
이를 통해
상위 클래스에서 알고리즘을 혼자 독점한다.
덕분에 서브 클래스에서 코드를 재사용할 수 있다.
공통적인 알고리즘은 한 군데에 모여있기 때문에, 그 부분만 고치면 된다.
일부 구현만 서브클래스에 의존한다.

정의

메소드에서 알고리즘의 골격을 정의한다.
여러 단계의 일부는 서브 클래스에서 구현할 수 있다. 알고리즘의 구조는 그대로 유지하면서 서브 클래스에서 특정 단계를 재정의할 수 있다.
이 때 후크란 개념이 있다.
후크란?
추상 클래스에서 선언되는 메소드긴 하지만 기본적인 내용만 구현되어있거나 아무 코드도 들어잇지 않은 메소드.
이렇게하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있다.

디자인 원칙

할리우드 원칙: 먼저 연락하지 마라. 우리가 연락하겠다.
→ 의존성 부패를 막을 수 있다.
고수준 구성요소가 저수준 구성요소에 의존하고, 그 저수준 구성요소는 고수준 구성요소에 의존하고. 이런 식으로 의존성이 복잡하게 꼬여있는 걸 의미한다.
할리우드 원칙을 사용하면 저수준 구성요소에서 시스템에 접속을 할 순 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 된다.
템플릿 메소드에서는 고수준 구성요소가 저수준 구성요소를 필요할 때마다 불러냄으로써 할리우드 원칙을 지킨다. 이 때, 클라이언트 입장에서는 알고리즘의 뼈대를 구상클래스가 아닌 추상클래스에 정의되어있는 메소드에 의존한다.

예시

→ 자바의 compareTo메소드
→ 구현 알고리즘 자체는 sort에 구현되어있지만, 비교하는 부분은 정렬될 객체에게 위임한다.

객체의 구현 방식을 캡슐화 할 수 있다.

이터레이터

동기

반복을 캡슐화하자. 객체 컬렉션의 형식이 다르기 때문에, 반복 작업을 하는 방법이 달라지는데, 이를 해결해보자.

정의

Iterator라는 인터페이스에 반복을 의존한다.
그리고 이 이터레이터 인터페이스를 구현한 특정 컬렉션을 위한 이터레이터 객체를 구현한다. 이 때, 이 이터레이터 객체는 특정 컬렉션을 구성으로 가지고 있다.
특정 컬렉션에서 createIterator를 호출하면 해당 컬렉션에 맞는 이터레이터 구현 클래스를 생성해서 준다.
→ 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공한다.

디자인 원칙

클래스를 바꾸는 이유는 한 가지 뿐이어야한다.
→ 클래스에 클래스의 역할 외에 다른 역할(반복자 메소드)를 처리하도록 하면, 두 가지 이유로 인해 그 클래스가 바뀔 수 있다.

컴포지트 패턴

동기

메뉴, 서브 메뉴, 메뉴 항목 등을 집어넣을 수 있는 트리 형태의 구조가 필요하다.
각 메뉴에 있는 모든 항목에 대해서 돌아가면서, 어떤 작업을 할 수 있는 방법을 제공해야하며, 그 방법은 반복자 정도로 편리해야한다.
더 유연한 방법으로 아이템에 대해서 반복작업을 수행할 수 있어야한다. 특정 객체 집합에 대해서만 반복자를 돌 수도 있어야한다.

정의

객체들을 트리구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다. 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체를 같은 방법으로 다룰 수 있다.

클래스 다이어그램-

클라이언트에서는 Component 인터페이스를 이용하여 복합 객체 내의 객체들을 조작할 수 있다.
Component에서는 복합 객체 내에 들어있는 모든 객체들에 대한 인터페이스를 정의, 복합 노드 뿐 아니라, 잎 노드에 대한 메소드까지 정의
잎에서는 add,remove,getChild같은 메소드가 전혀 쓸모가 없음에도 불구하고, 그 메소드를 상속받아야한다. → 조금 있다가
Composite는 자식이 있는 구성요소의 행동을 정의하고 자식 구성요소를 저장하는 역할
관리할 때는 모두 같은 Component 타입으로 관리한다.

정리

컴포지트 패턴에서는 단일 역할 원칙을 깨면서 대신 투명성을 확보하기 위한 패턴이다.
Component 인터페이스에 자식들을 관리하기 위한 기능과 잎으로써의 기능을 전부 집엉넣음으로써 클라이언트에서 복합 객체와 잎 노드를 똑같은 방식으로 처리한다. 이러면 안전성은 약간 떨어진다.
대신에 여러 역할을 서로 다른 인터페이스로 분리할 수도 있는데, 이러면 투명성이 떨어지고 코드에 조건문이라든가 instanceof 연산자 같은 걸 써야만 한다.

객체의 수를 캡슐화할 수 있다. 클라이언트 입장에서 이것이 복합객체인지, 단일 객체인지 신경쓸 필요가 없다.

스테이트 패턴

스트래티지 패턴은 바꿔 쓸 수 있는 알고리즘을 내세워 큰 성공을 거뒀다.
하지만 스테이트 패턴은 내부 상태를 바꿈으로써 객체에게 행동을 바꾸는 것을 도와준다.

동기

상태를 if-else 문으로 처리하면, 상태가 추가되거나 바뀌거나, 기능이 추가됬을 때 다 고쳐야한다.
→ 모든 행동에 대한 메소드가 들어있는 State 인터페이스를 정의해야함.
→ 기계의 모든 상태에 대해서 상태 클래스를 구현해야한다. 기계가 어떤 상태에 있다면 그 상태에 해당하는 상태 클래스가 모든 작업을 책임져야 된다.
→ 조건문을 전부 없애고 상태 클래스에 모든 작업을 위임해야한다.

정의

객체 내부의 상태가 바귐에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

다이어그램

Context 라는 클래스에는 여러 가지 내부 상태가 들어있을 수 있다.
State 인터페이스에서는 모든 구상 상태 클래스에 대한 공통 인터페이스를 정의.
ConcreteState에서는 context로 부터 전달된 요청을 처리한다. 각 요처을 처리하는 방법을 자기 나름의 방식으로 구현한다.
context의 request 메소드가 호출되면 그 작업은 상태 객체에 맡겨진다.

프록시 객체

객체에 대한 접근을 제어한다.

정의

어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴

다이어그램

Proxy에서 RealSubject의 인스턴스를 생성하거나, 그 객체의 생성 과정에 관여하는 경우가 많다.
Proxy에는 실제 작업을 처리하는 객체에 대한 레퍼런스가 들어있다. 실제 객체가 필요하면 그 레퍼런스를 이용해서 요청을 전달한다.
RealSubject는 실제 작업을 대부분 처리하는 객체를 나타낸다. Proxy는 그 객체에 대한 접근을 제어하는 객체다.

패턴의 분류

생성

객체 인스턴스 생성을 위한 패턴으로 클라이언트와 그 클라이언트에서 생성해야할 객체 인스턴스 사이의 연결을 끊어주는 패턴이다.
Ex) 싱글턴, 추상 팩토리, 팩토리 메소드

행동

클래스와 객체들이 상호작용하는 방법 및 역할을 분담하는 방법과 관련된 패턴
Ex) 템플릿 메소드, 커맨드, 이터레이터, 옵저버, 스테이트, 스트레티지

구조

클래스 및 객체들을 구성을 통해서 더 큰 구조로 만들 수 있게 해주는 것들이다.
Ex) 데코레이터, 컴포지트, 프록시, 퍼사드, 어댑터