티스토리 뷰
아직 한참 읽고 있는 도중이기는 하지만 Head First Design Patterns 라는 책이 디자인패턴에 대한 설명 뿐만 아니라, 해당 디자인 패턴을 도입하게 되기까지의 시나리오를 유머러스하게 잘 풀어 이야기 해 주어서 빠르고 쉽게 이해하고 받아들일 수 있게 해 주는 것 같다.
아직 읽은 부분보다 읽어야 하는 부분이 산더미 처럼 많은데, 점차 책을 읽어나가면서 현재 글을 쓰면서 부족하게 작성하고 넘어간 부분들은 다시 업데이트 하면서 추후 복습하려고 한다.
Strategy Patterns
알고리즘군(群)을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든 디자인 패턴. 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
알고리즘군(群)을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든 디자인 패턴. 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

이렇게 하면 소리를 낼 수 있는지, 헤엄을 칠 수 있는지에 따라 해당 인터페이스만 구현하면 되고, 새로운 기능이 필요(하늘을 나는 등)할 경우 Flyable 인터페이스를 새로 작성하여 이를 implements 해 주면 되니까 충분하지 않나? 하는 생각이 들었다.
하지만 바로 다음 페이지에 내 뒤통수를 쎄게 때려주는 한마디가 적혀 있었다. (학부생 시절에는 작성한 코드가 잘 돌아가는지, 결과는 어떤지에 대해서만 평가를 받았지, 구조에 대해서는 평가받지 않았는데 아마 그랬다면 한번쯤은 F를 받지 않았을까...?)
이건 지금까지 나왔던 아이디어 가운데 제일 바보같은 아이디어군요. 코드 중복은 생각도 못했나요?
이렇게 할 경우 각각의 하위 클래스에서 모든 행동을 매번 정의해야 하므로 코드가 전혀 재사용되지 못하는 문제가 발생한다. 지금 보면 너무 당연한 것인데 왜 바로 못 떠올렸는지 모르겠다.
이를 strategy pattern에 맞게 각각의 알고리즘군(행동)으로 분류하고 캡슐화하면 다음과 같은 구조로 개선할 수 있다.

이렇게 구조를 짜 두고, 각 클래스의 인스턴스가 생성 될 때 마다 QuackBehavior, SwimBehavior의 인스턴스로 적절한 구현체를 선택해서 가지고 있도록 해 주기만 하면 원하는 다른 부분의 코드를 수정하지 않고 원하는 대로의 동작을 하도록 할 수 있다.
뿐만 아니라 가지고 있는 행동 객체를 바꿔줌으로써 얼마든지 원하는 타이밍에 수행하는 동작을 바꿔줄 수도 있다.각 클래스는 인터페이스에만 의존하고 있기 때문에, 세부 객체는 몰라도 해당 인터페이스에 속한 함수만 실행할 수 있으면 아무런 문제도 없다.
Observer Pattern
옵저버 패턴은 신문사와 정기구독자로 이루어지는 신문 구독 서비스에 비유해서 생각하면 된다.
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(One-to-Many) 의존성을 정의한다.
구현 방법에는 대체로 주제(Subject) 인터페이스와 옵저버(Observer) 인터페이스가 들어있는 클래스 디자인을 바탕으로 한다. 이 클래스 다이어그램은 아래 그림과 같다

주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다고 한다.
사실 여기까지만 봤을 땐 잘 와닿지 않았는데, wikipedia에서 이 Observer를 listener라고도 불린다고 한 부분에서 느낌이 확 왔다.
JS에서 여러 eventListener를 웹 페이지 여기저기에 (필요에 따라) 막 붙여주게 되는데, 가령 마우스 클릭 이벤트에 대한 리스너를(옵저버를) 붙여 주었다면, 이제 해당 영역(주제)에서 클릭이 발생 할 때 마다 해당 옵저버는 콜백을 받아 상태가 변화 했음을(클릭이 발생 했음을) 알게 된다.
지금 내 상태에서는 옵저버 패턴을 일단 JS의 이벤트 리스너의 역할로 이해하고 있으면 적절할 것 같다. 이후 실습 코드 작성 등을 해 보면서 좀 더 공부를 해 봐야 할 듯 하다.
Decorator Pattern
객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
Head First Design Patterns 에서 이 디자인패턴의 도입이 필요한 상황 설정으로 다음과 같은 설정을 주었다.
- 스타X스 음료 관리 프로그램
- 모든 음료 메뉴는 상위의 Beverage 클래스를 상속한다.
- 각 메뉴를 나타내기 위한 각각의 클래스가 존재한다.
- 아아, 뜨아, 에스프레소, 디카페인커피, 오늘의커피, 휘핑크림이 올라간... 등등 수십가지 이상의 메뉴가 존재 = 수십개의 클래스가 존재
이렇게 들어가는 재료가 각각 다른 수십개의 음료를 표현하는 수십개의 클래스를 일일이 가지고 있는것은 굉장히 비효율적이다. 이런 문제를 해결해 줄 수 있는 디자인 패턴이 데코레이터 패턴이다.
우선, 책에서 제시 해 주는 클래스 다이어그램은 다음과 같다.

잘 보면 ConcreteDecorator 클래스들은 원래 자기 자신이었던 Conponent 클래스의 인스턴스를 가지고 있다. 이는 기존의 정보는 그대로 두고, 확장된 정보를 표현하기 위한 방법이다.
Component를 Coffee 라고 생각하고, ConcreteDecoretorA를 ShotAddedCoffee, ConcreteDecoratorB를 MilkAddedCoffe 라고 가정 해 보자.
또, methodA() 는 cost() 메서드이고, 음료의 가격을 반환 해 준다고 생각 해 보자.
그러면, 한 손님이 가게에서 (1) 커피를 주문하는데, (2) 샷을 두 번 추가하고, (3) 우유도 추가하는 경우 다음과 같은 진행과정을 거치게 된다.
Beverage order = new Coffee(); // (1) 커피 주문
order = new ShotAddedCoffee(order); // (2) 샷 추가 (1/2회)
order = new ShotAddedCoffee(order); // (2) 샷 추가 (2/2회)
order = new MilkAddedCoffe(order); // (3) 우유 추가
한 줄 한 줄 내려갈수록 이전 줄의 결과(객체)를 내부 멤버 변수로 가지고 있는 상태로 Wrapper class 같은 형태로 확장되게 된다.
즉, 최종적으로 order.cost() 등의 방법으로 가격을 계산하게 되면 먼저 우유의 가격을 추가한 후 내부에 저장하고 있는 ShotAddedCoffee 클래스의 인스턴스의 cost() 메서드를 호출하고, 다시 샷 추가 가격을 추가한 뒤 멤버 변수로 가지고 있는 wrapperObj의 cost() 메서드를 호출해 나가는 과정을 반복하여 결과적으로 [우유 추가 가격 + 샷 추가 가격 + 샷 추가 가격 + 커피 기본 가격] 의 결과를 얻을 수 있다.
Factory Method Pattern
팩토리 메서드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다.
팩토리 메서드 패턴을 사용하면 구상 형식의 인스턴스를 만드는 작업을 캡슐화 할 수 있다. 이를 나타내는 클래스 다이어그램은 다음과 같다.

Head First Design Patterns 에서 소개하고 있는팩토리 메서드 패턴을 활용한 피자 가게 클래스 다이어그램은 아래와 같다.

이 다이어그램을 보면 지역마다 다른 제품을 만들 수 있게 하기 위해 팩토리 메서드 패턴을 사용하여 각 지역마다 그 지역에 맞는 피자를 만들 수 있는 구상 팩토리를 만들도록 했다. 이 때 클라이언트는 Pizza 라는 추상 형식에만 의존한다.
Abstract Factory Pattern
인터페이스를 이용하여 서로 연관되거나 의존하는 객체를 구상 클래스를 지정하지 않고 생성할 수 있도록 한다.
추상 팩토리 패턴을 이용하면 클라이언트에서 추상 인터페이스를 통해서 일련의 제품들을 공급받을 수 있다. 실제로 어떤 제품이 생산되는지는 알 필요가 없다. 이를 통해 클라이언트와 팩토리에서 생산되는 제품을 분리시킬 수 있다.
Factory Method Pattern & Abstract Factory Pattern
간단하게 둘의 특징을 살펴보자면 다음과 같다.
Factory Method Pattern | Abstract Factory Pattern |
Application을 특정 구현으로부터 분리시키는 역할을 한다. 이를 통해 클라이언트에서는 자신이 사용할 추상 형식만 알면 되고, 구상 형식은 서브클래스에서 알아서 처리하도록 해 줄 수 있다. (클라이언트와 구상 형식을 분리시켜주는 역할) | |
객체 생성을 캡슐화하여 Application의 결합을 느슨하게 하고, 특정 구현에 대한 의존도를 낮춘다. | |
상속을 통해 객체를 만든다. | 객체 구성(composition)을 통해 객체를 만든다. |
클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야 할 때 활용하면 좋다. | 제품군을 만들기 위한 추상 형식을 제공한다. 제품이 생상되는 방법은 이 형식의 서브클래스에서 정의한다. |
어떤 구상 클래스를 필요로 하게 될 지 미리 알 수 없는 경우에도 유용하다. | 새로운 제품을 추가하려면 인터페이스를 바꿔야 한다. |
팩토리 메서드가 있는 클래스의 서브 클래스를 만들고, 팩토리 메서드를구현함으로써 사용할 수 있다. | 클라이언트에서 서로 연관된 일련의 제품들을 만들어야 할 때, 즉 제품군(群)을 만들어야 할 때 활용한다. |
Singleton Pattern
해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴이다.
싱글톤 패턴의 몇가지 조건을 먼저 살펴보면 다음과 같다.
- 클래스에서 단 하나뿐인 인스턴스를 관리하도록 만들어야 한다.
- 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야 한다.
→ 인스턴스가 필요하면 반드시 클래스 자신을 거치도록 한다.
- 어디서든 인스턴스에 접근할 수 있어야 한다.
→ 다른 객체에서 이 인스턴스가 필요하면 언제든지 클래스에게 요청할 수 있게 하고, 요청이 들어오면 하나뿐인 인스턴스를 전해주도록 만들어야 한다.
구현방식
먼저, 학교에서 OOP 강의를 할 때에는 아래 코드처럼 singleton을 구현하도록 배웠었다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
하지만 이렇게 코드를 작성 할 경우, 멀티 쓰레드 환경에서 동시에 getInstance() 메서드가 호출되었을 때, 어느 한 쓰레드에서 uniqueInstance = new Singleton() 이 호출되기 전에 다른 쓰레드에서 if (uniqueinstance == null) 이라는 조건을 통과해버릴 가능성이 존재한다. 즉, 2개의 쓰레드에서 동시에 if (uniqueinstance == null) 이라는 조건문에 접근하게 된다면 2개의 Singleton 인스턴스가 생길 수 있다.
이 문제는 synchronized 키워드를 붙여 멀티쓰레드 환경에서도 동기화가 이루어지게 해 주거나, 인스턴스의 생성을 인스턴스가 필요한 시점이 아니라 Application의 실행 시점으로 앞당김으로써 해결할 수 있다. 이 경우 다음과 같은 코드가 된다.
public class Singleton {
// 방법 1. Application 실행 시점에 싱글톤 인스턴스가 생성되게 한다.
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
// 방법 2. synchronized 키워드를 통해 getInstance 메서드의 호출을 동기화한다.
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
- 추가로 공부해 볼 사항 : DCL(Double-Checking Locking)
Command Pattern
커맨트 패턴을 통해 요구 사항을 객체로 캡슐화 할 수 있고, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수 있으며 작업 취소 기능도 지원이 가능하다.
커맨드 객체는 일련의 행동을 특정 리시버하고 연결시켜 요구사항을 캡슐화 한 것이다. 이를 위해 행동과 리시버를 하나의 객체에 집어넣고, execute()라는 메서드 하나만 외부에 공개하는 방법을 사용한다. 이 메서드가 호출되면 리시버에서 일련의 작업이 처리된다. 외부에서는 어떤 객체가 리시버 역할을 하는지, 리시버에서 실제로 어떤 일을 하는지 알 수 없다.
커맨드 패턴의 간단한 클래스 다이어그램은 다음과 같다.

이렇게 봤을 때 어떤 식으로 굴러가는 건지 감이 잘 오지 않았는데, 다음과 같은 예시로 받아들이니 이해가 한결 편했다.
- Client = 애플 홈팟 등의 기기
- Invoker = Controller (Home 앱 등 리모컨 역할)
- Receiver = 원격 컨트롤이 가능한 ioT 기기
- ConcreteCommand = 각 기기별 기능 (전원 of/off, 음량 조절, 채널 변환, 밝기 조절 등...)
Adapter Pattern
한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다. 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 사용할 수 있도록 해 준다.
어댑터 패턴을 이용하면 클라이언트와 구현된 인터페이스를 분리할 수 있으며, 나중에 인터페이스가 바뀌더라도 그 변경내역이 어댑터에 의해 캡슐화되어 클라이언트는 바뀌지 않아도 되게 해 준다.
어댑터 패턴의 클래스 다이어그램은 다음과 같다.

Facade Pattern
어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다. 퍼사드에서 고수준의 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있다.
이는 다음과 같은 클래스 다이어그램으로 표현할 수 있다.

퍼사드 패턴을 사용하면 클라이언트 코드가 직접 상호작용 하는 클래스를 최소화 할 수 있고, 이 덕분에 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 되는 상황을 막을 수 있다. 관리나 코드 파악의 용이성 향상도 기대할 수 있다. 이는 다음 객체 지향 원칙을 지킨 것이기도 하다.
최소 지식 원칙(Principle of Least Knowledge)
어떤 한 객체와 다른 객체들관의 연관관계를 최소화 하라.
이를 위한 다음과 같은 가이드라인이 있다.
어떤 메서드에서든지 다음 네 종류의 객체의 메서드만을 호출하라.
- 객체 자체
- 메서드에 매개변수로 전달된 객체
- 그 메서드에서 생성하거나 인스턴스를 만든 객체
- 그 객체에 속하는 구성요소
이 원칙을 잘 지킬 경우 객체들 사이의 의존성을 줄여 소프트웨어의 관리를 좀 더 용이하게 해 주지만, 다른 구성요소에 대한 메서드 호출을 처리하기 위해 Wrapper class를 더 필요로 할 수도 있다. 이 경우 오히려 시스템이 더 복잡해지고 성능저하가 발생할 가능성이 있다.
Template Method Pattern
즉, 알고리즘의 틀을 만들기 위한 디자인 패턴이다. 여기서 틀(템플릿)이란, 일련의 단계들로 알고리즘을 정의한 메서드이다. 여러 단계 가운데 하나 이상이 추상 메서드로 정의되며 그 추상 메서드는 서브 클래스에서 구현된다. 이렇게 하면 서브클래스에서 일부분을 구현할 수 있도록 하면서도 알고리즘의 구조는 바꾸지 않아도 되도록 할 수 있다.
이 패턴의 클래스 다이어그램은 다음과 같다.

이 때, subclass는 일부 자질구레한 메서드 구현을 제공하기 위한 용도로만 사용된다.
즉, 서브클래스들은 직접 호출을 당하기 전 까지 절대로 상위 추상 클래스를 직접 호출하지 않아야 한다.
Iterator Pattern
컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공한다.
또, 이터레이터 패턴을 사용하면 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아닌 이터레이터 객체에서 맡게 되는데, 이렇게 되면 집합체의 인터페이스 및 구현이 간단해지는 이점이 있다.
이 패턴의 클래스 다이어그램은 다음과 같다.

다른 장점으로, 이터레이터 패턴을 이용하면 다형적인 코드를 만들 수 있다. 이는 Iterator를 매개변수로 받는 메서드를 만들면, 어떤 컬렉션에 대해서도 사용할 수 있는 코드가 된다.
Composite Pattern
객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층 구조로 만들 수 있다. 이 패턴을 사용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체(Composite)를 똑같은 방법으로 다룰 수 있다.
즉, 객체의 구성과 개별 객체를 노드로 가지는 트리 형태로 객체를 구축하는 방법이다. 이러한 복합 구조(composite structure)를 사용하면 복합 객체와 개별 객체에 대해 똑같은 작업을 적용할 수 있다.(대부분의 경우에 복합 객체와 개별 객체를 구분할 필요가 없어진다.)
간단히 말하면 부분-전체 관계를 가지는 객체 컬렉션이 있고, 그 객체들을 모두 똑같은 방식으로 다루고 싶을 때 사용하는 패턴이다.
컴포지트 패턴의 클래스 다이어그램은 아래와 같다.

컴포지트 패턴을 사용함으로써 얻을 수 있는 장점은 클라이언트를 단순화 시킬 수 있다는 점이다. 이 패턴을 사용하면 클라이언트는 봅합 객체를 사용하고 있는지, 잎 객체를 사용하고 있는지에 대해서 생각 할 필요가 없다. 또, 메서드 하나만 호출하면 전체 구조에 대해서 반복해서 작업을 처리할 수 있도록 할 수도 있다.
State Pattern
객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿔 주기 위한 패턴. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.
이 패턴에서는 상태를 별도의 클래스로 캡슐화 한 다음 현재 상태를 나타내는 객체에게 행동을 위임한다. 따라서 내부 상테가 바뀜에 따라 행동이 달라지게 된다.
스테이트 패턴의 클래스 다이어그램은 다음과 같다.

클래스 다이어그램의 형태는 Strategy Pattern과 동일하다. 하지만 이 두 패턴 사이에는 용도의 차이가 존재한다.
스테이트 패턴은 상태 객체에 일련의 행동이 캡슐화된다. 그래서 상황에 따라 Context 객체에서 여러 상태 객체 중 하나에게 모든 행동을 맡기게 된다. 그 객체의 내부 상태에 따라 현재 상태를 나타내는 객체가 바뀌고, 그 결과 컨텍스트 객체의 행동도 바뀌게 된다. 클라이언트는 상태 객체에 대해 거의 아무것도 몰라도 무방하다.
반대로 스트래티지 패턴은 클라이언트에서 컨텍스트 객체한테 어떤 전략 객체를 사용할지 지정해 준다. 즉, 주로 실행시에 전략 객체를 변경할 수 있는 유연성을 제공하기 위한 용도로 사용된다.
다른 말로, 스트래티지 패턴은 상속을 이용해서 클래스의 행동을 정의하다 보면 점점 행동을 변경하기가 어려워 지므로 구성을 통해 행동을 정의하는 객체를 유연하게 바꿀 수 있도록 해 주기 위해 사용된다.
이에 반해, 스테이트 패턴은 객체에 (상태를 확인하는) 수많은 조건문을 넣는 대신에 사용하는 패턴이라고 생각하면 좋다. 행동을 상태 객체 내에 캡슐화시켜 컨텍스트 내의 상태 객체를 바꾸는 것만으로도 컨텍스트 객체의 행동을 바꿀 수 있도록 하기 위해 사용되는 패턴이라고 생각하자.
Proxy Pattern
어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
프록시 패턴은 접근을 제어하는 방법 면에서 차이가 있는 몇가지 방법이 존재한다.
- 원격 프록시를 사용하여 원격 객체에 대한 접근을 제어하는 방법
- 가상 프록시(virtual proxy)를 사용하여 생성하기 힘든 자원에 대한 접근을 제어하는 방법
- 보호 프록시(protection proxy)를 사용하여 접근 권한이 필요한 자원에 대한 접근을 제어하는 방법
- etc.
자세한 내용은 추후 다시 공부를 하면서 살펴보고, 정리하려고 한다.
프록시 패턴의 클래스 다이어그램은 아래와 같이 그려진다.

- Proxy와 RealSubject 모두 Subject 인터페이스를 구현하게 함으로써 어떤 클라이언트에서도 프록시를 주 객체하고 똑같은 방식으로 다룰 수 있다.
- RealSubject는 실제 작업을 대부분 처리하는 객체이다. Proxy는 이 객체에 대한 접근을 제어하는 객체이다.
Bridge Pattern
구현 뿐만 아니라 추상화된 부분까지 변경시켜야 하는 경우에 유용한 패턴. 추상화된 부분과 추상 클래스/인터페이스를 구현한 클래스를 서로 다른 클래스 계층구조에 집어넣어 둘을 모두 변경시킬 수 있도록 한다.
여러 플랫폼에서 사용해야 하는 그래픽스 및 윈도우 처리 시스템에서 유용하게 사용되며, 인터페이스와 실제 구현부를 서로 다른 방식으로 변경해야 하는 경우 유용하다.
장점 | 단점 |
구현을 인터페이스에 완전히 결합시키지 않았기 떄문에 구현과 추상화된 부분을 분리시킬 수 있다. | 디자인이 복잡해진다. |
추상화된 부분과 실제 구현 부분을 독립적으로 확장할 수 있다. | |
추상화된 부분을 구현한 구상 클래스를 바꿔도 클라이언트 쪽에는 영향을 미치지 않는다. |
리모콘과 TV를 예로 들었을 때, 클래스 다이어그램은 다음과 같이 표현된다.

Builder Pattern
제품을 여러 단계로 나눠서 만들 수 있도록 제품 생산 단계들을 캡슐화 해 준다.
이터레이터 패턴과 같은 아이디어를 사용한다. 수행해야하는 작업을 빌더 객체에 캡슐화 시켜서 클라이언트에서는 빌더에게 요청만 하면 되도록 한다. 그래서 복합 객체 구조를 구축하기 위한 용도로 많이 사용된다.
장점 | 단점 |
복합 객체가 생성되는 과정을 캡슐화한다. | 팩토리 패턴을 사용하는 경우에 비해 객체를 만들기 위해서 클라이언트에 대해 더 많이 알아야 한다. |
여러 단계와 다양한 절차를 통해서 객체를 만들 수 있다. (팩토리 패턴에서는 한 단계에서 모든걸 처리해야 한다.) | |
제품의 내부 구조를 클라이언트로부터 보호할 수 있다. | |
클라이언트에서는 추상 인터페이스만 볼 수 있기 때문에 제품을 구현한 코드를 쉽게 바꿀 수 있다. |
클래스 다이어그램은 다음과 같은 형태로 나타난다.

Chain of Responsibility Pattern
하나의 요청을 두 개 이상의 객체에서 처리하고 싶을 떄 사용하는 디자인 패턴
동작 과정은 switch 문을 생각하면 될 듯 하며, 클래스 다이어그램은 다음과 같은 형태로 나타난다.

시나리오는 switch 문 동작 방식처럼, 어떤 이메일이 들어오면 먼저 SpamHandler에 의해 스팸메일인지 걸러진 후, 다음 handler인 FanHandler, ComplainHandler, NewLocHandler로 차례차례 전해진다. 만일 마지막 핸들러에서도 처리되지 않는 경우가 발생한다면 default 문을 넣듯이 모든 요청을 처리하는 핸들러를 구현 할 수도 있을 것이다.
윈도우 시스템의 마우스 클릭이나 키보드 이벤트를 처리할 때 흔히 사용된다. (JS를 가볍게라도 공부 해 두면 이런 부분에서 여러모로 도움이 많이 되는 것 같다.)
장점 | 단점 |
요청을 보낸 쪽하고 받는 쪽을 분리시킬 수 있다. | 동작 과정은 switch 문을 생각하면 될 듯 하며, 클래스 다이어그램은 다음과 같은 형태로 나타난다. |
객체에서는 사슬의 구조를 몰라도 되고, 그 사슬에 들어 있는 다른 객체에 대한 직접적인 레퍼런스를 가질 필요도 없기 때문에 객체를 단순하게 만들 수 있다. | |
사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거할 수 있다. | 실행 시에 과정을 살펴보거나 디버깅 하는게 어려울 수 있다. |
Flyweight Pattern
어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 "가상 인스턴스"를 제공하고 싶을 때 사용하는 디자인 패턴
어떤 클래스의 인스턴스가 아주 많이 필요하지만, 모두 똑같은 방식으로 제어할 수 있는 경우에 유용하다.
장점 | 단점 |
실행시에 객체 인스턴스의 갯수를 줄여 메모리를 절약할 수 있다. | 특정 인스턴스만 다른 인스턴스와 다른 방식으로 행동하도록 하는 것이 불가능하다. |
여러 "가상" 객체의 상태를 한 곳에 집중시켜 놓을 수 있다. |
만일 어떤 게임을 만드는 툴에서 배경에 사물을 배치하는 기능을 구현한다면, 다음과 같은 클래스 다이어그램을 그릴 수 있다.

Interpreter Pattern
어떤 언어에 대한 인터프리터를 만들 때 사용한다.
학부생 시절 컴파일러 설계 수업을 들으면서 교수님이 제공해주신 MiniC 언어를 읽고 그 동작을 Java code로 수행하게 만드는 과제가 있었다. 그 때는 당장 눈앞에 닥친 기능 하나 하나를 구현하는데 바빠 미처 몰랐는데, 인터프리터 패턴을 이해하고 있었다면 수업을 좀 더 깊이있게 들을 수 있었을 것 같다.
이처럼 간단한 언어를 구현할 때 인터프리터 패턴이 유용하게 사용되는 것 같다.
문법이 간단하고 효율보다는 단순하게 만드는 것이 더 중요한 경우에 유용하며, 스크립트 언어 및 프로그래밍 언어 모두에서 사용 가능하다.
장점 | 단점 |
각 문법 규칙을 클래스로 표현하기 때문에 언어를 쉽게 구현할 수 있다. | 문법 규칙의 갯수가 많아지면 복잡해진다. (이 경우 Parser/Compiler 생성기를 사용하는 것이 좋다.) |
문법이 클래스에 의해 표현되기 때문에 언어를 쉽게 변경하거나 확장할 수 있다. | |
클래스 구조에 메서드만 추가하면 프로그램을 해석하는 기본 기능 외에 원하는 추가 기능을 넣을 수 있다. |
Mediator Pattern
서로 관련된 객체 사이의 복잡한 통신과 제어를 한 곳으로 집중시키고자 하는 경우 사용하는 패턴이다.
예를 들어, 애플 홈팟 등을 사용하여 가정 내 ioT 기기들을 사용하려 하는데, 여러가지 기능이 자동으로 이어지도록 하고 싶다. 내가 오전 10시에 TV를 키라는 명령을 하면, 아이폰은 TV에 10시에 전원 on 하라는 명령을 하고, TV는 욕실 관리 기기에 9시까지 욕조에 물을 받아놓으라고 하고, 욕실 관리 기기는 다시 커피머신에 9시 50분에 커피 한잔을 세팅 해 놓으라고 하고, 커피 머신은 커피가 완성되었다는 메세지를 아이폰에 보내고, 아이폰은 다시 그 메세지를 받은 다음 에어컨 전원을 키라는 메세지를 날리게 하고 싶다.
이정도 연속 규칙이라면 어떻게 만들 수 있을지 모르지만, 원해는 규칙들이 점점 많아진다면? 또는 어떤 명령이 들어왔을 때 이것이 어떤 연속 규칙에 의해 들어 온 건지는 어떻게 알 수 있는지? 등의 문제가 따라오게 된다.
이럴 때, 미디에이터 패턴을 사용하면 ioT 기기들(객체들)을 훨씬 단순화 시킬 수 있다.

이런 식으로 이루어지던 통신을 다음과 같이 간단하게 만들 수 있다.

Memento Pattern
객체를 이전 상태로 복구시켜야 하는 경우에 이용하는 패턴이다.
메멘토 패턴에는 두 가지 목적이 있다.
하나는 시스템에서 핵심적인 기능을 담당하는 객체의 중요한 상태를 저장하는 것이고, 다른 하나는 핵심적인 객체의 캡슐화를 유지하는 것이다.
이 때, 상태를 따로 저장하는 역할을 맡는 객체를 메멘토 객체라고 한다.
메멘토 패턴의 클래스 다이어그램은 아래와 같은 형태를 가진다. 아래 다이어그램은 게임의 예시이다. 게임의 Save/Load 기능을 위한 저장을 생각하면 된다.

장점 | 단점 |
저장된 상태를 핵심 객체와는 다른 별도의 객체에 보관하기 떄문에 안전하다. | 상태를 저장하고 복구하는 데 시간이 오래 걸릴 수 있다. |
핵심 객체의 데이터를 계속해서 캡슐화된 상태로 유지할 수 있다. | 자바 시스템에서는 시스템의 상태를 저장할 때 직렬화를 사용하는 것이 좋다. |
복구 기능을 구현하기가 상대적으로 쉽다. |
Prototype Pattern
어떤 클래스의 인스턴스를 만드는 것이 자원/시간을 많이 잡아먹거나 복잡할 경우 사용하는 패턴이다.
프로토타입 패턴을 이용하면 기존 인스턴스를 복사하는 것 만으로 새로운 인스턴스를 만들 수 있다. 또한 클라이언트 코드에서 어떤 클래스의 인스턴스를 만드는지 전혀 모르는 상태에서도 새로운 인스턴스를 만들 수 있다는 특징이 있다.
특히, 시스템에서 복잡한 클래스 계층구조 속에 파묻혀 있는 다양한 형식의 인스턴스를 새로 만들어야 하는 경우에 유요하게 써먹을 수 있다.
장점 | 단점 |
클라이언트에서는 새로운 인스턴스를 만드는 복잡한 과정을 몰라도 된다. | 객체의 복사본을 만드는 일이 매우 복잡한 경우가 있을 수 있다. |
클라이언트에서는 구체적인 형식을 모르더라도 객체를 생성할 수 있다. | |
상황에 따라서 객체를 새로 생성하는 것 보다 객체를 복사하는 것이 더 효율적일 수 있다. |
Visitor Pattern
다양한 객체에 새로운 기능을 추가해야 하는데 캡슐화가 별로 중요하지 않은 경우에 사용하기 좋은 패턴
Visitor 객체는 Traverser 객체와 함께 움직인다. Traverser는 컴포지트 패턴을 쓰는 경우 복합 객체 내에 속해 있는 모든 객체들에 대해 접근하는 것들 도와주는 역할을 한다.
각각의 객체의 상태를 모두 가져오고 나면 클라이언트에서는 Visitor로 하여금 각 상태들에 대해 다양한 작업을 처리하도록 요구할 수 있다.
새로운 기능이 필요하게 되는 경우라도 Visitor 내부만 고치면 된다.

장점 | 단점 |
구조 자체를 변경시키지 않으면서도 복합 객체 구조에 새로운 기능을 추가할 수 있다. | Visitor를 사용하면 복합 클래스의 캡슐화가 깨진다. |
비교적 손쉽게 새로운 기능을 추가 할 수 있다. | 컬렉션 내의 모든 항목을 접근하기 위한 Traverser가 있기 때문에 복합 구조를 변경하기가 더 어렵다. |
Visitor에서 수행하는 기능과 관련된 코드를 한 곳에 집중시켜 놓을 수 있다. |
'Programmers 데브코스 > 더 알아보기' 카테고리의 다른 글
Java의 Object class 훑어보기 (0) | 2021.08.07 |
---|---|
[Java] StringBuffer와 StringBuilder (0) | 2021.08.06 |
- Total
- Today
- Yesterday
- programmers
- Hash
- dynamic programming
- 힙
- Heap
- 백준
- 탐욕법
- 연습문제
- dfs
- 정렬
- Sorting
- DP
- 데브코스
- 해시
- 코딩테스트
- BFS
- java
- 그래프
- 자바
- Queue
- 멀리 뛰기
- 완전탐색
- 큐
- Algorithm
- 동적계획법
- 알고리즘
- greedy
- stack
- 자료구조
- 프로그래머스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |