디자인 패턴은 프로그램을 개발하는 과정에서 빈번하게 발생하는 디자인 문제를 정리해서 상황에 따라 간편하게 적용할 수 있게 정리한 것입니다. 잘 활용할 수만 있다면 적지 않은 시간과 노력, 시행착오를 줄일 수 있습니다.
모든 패턴은 패턴의 의도 또는 목적에 따라 분류할 수 있습니다.
생성 패턴들은 기존 코드의 재활용과 유연성을 증가시키는 객체 생성 메커니즘들을 제공합니다.
구조 패턴은 구조를 유연하고 효율적으로 유지하면서 객체와 클래스를 더 큰 구조로 조합하는 방법을 설명합니다.
행동 패턴은 객체 간의 효과적인 의사소통과 책임 할당을 처리합니다.
1) Builder : 생산 단계를 캡슐화 하여 구축 공정을 동일하게 이용하도록 하는 패턴
2) Prototype : 복사하여 새 개체를 생성할 수 있도록 하는 패턴
3) Factory Method : 객체를 생성하기 위한 인터페이스를 정의하여 어떤 클래스가 인스턴스화 될 것인지는 서브 클래스가 결정하도록 하는 패턴
4) Abstract Method : 생성군들을 하나의 모아놓고 팩토리 중에서 선택하게 하는 패턴
5) Singleton : 유일한 하나의 인스턴스를 보장하도록 하는 패턴
1) Bridge : 추상과 구현을 분리하여 결합도를 낮춘 패턴
2) Decorator : 소스를 변경하지 않고 기능을 확장하도록 하는 패턴
3) Facade : 하나의 인터페이스를 통해 느슨한 결합을 제공하는 패턴
4) Flyweight : 대량의 작은 객체들을 공유하는 패턴
5) Proxy : 대리인이 대신 그 일을 처리하는 패턴
6) Composite : 개별 객체와 복합 객체를 클라이언트에서 동일하게 사용하도록 하는 패턴
7) Adapter : 인터페이스로 인해 함께 사용하지 못하는 클래스를 함께 사용하도록 하는 패턴
1) Interpreter : 언어 규칙 클래스를 이용하는 패턴
2) Template Method : 알고리즘 골격의 구조를 정의한 패턴
3) Chain of Responsibility : 객체들끼리 연결 고리를 만들어 내부적으로 전달하는 패턴
4) Command : 요청 자체를 캡슐화하여 파라미터로 넘기는 패턴
5) Iterator : 내부 표현은 보여주지 않고 순회하는 패턴
6) Mediator : 객체 간 상호작용을 캡슐화한 패턴
7) Memento : 상태 값을 미리 저장해 두었다가 복구하는 패턴
8) Observer : 상태가 변할 때 의존자들에게 알리고, 자동 업데이트하는 패턴
9) State : 객체 내부 상태에 따라서 행위를 변경하는 패턴
10) Strategy : 다양한 알고리즘 캡슐화하여 알고리즘 대체가 가능하도록 한 패턴
11) Visitor : 오퍼레이션을 별도의 클래스에 새롭게 정의한 패턴
점층적 생성자'를 제거하기 위하여 빌더 패턴을 사용하세요.
빌더 패턴을 사용하면 실제로 필요한 단계들만 사용하여 단계별로 객체들을 생성할 수 있으며, 패턴을 구현한 후에는 더 이상 수십 개의 매개변수를 생성자에 집어넣을 필요가 없습니다.
빌더 패턴은 제품의 다양한 표현의 생성 과정이 세부 사항만 다른 유사한 단계를 포함할 때 적용할 수 있습니다.
빌더는 생성 단계들을 수행하는 동안 미완성 제품을 노출하지 않으며, 이는 클라이언트 코드가 불완전한 결과를 가져오는 것을 방지합니다.
당신의 객체들에 수십 개의 필드와 수백 개의 가능한 설정들이 있는 경우 이를 복제하는 것이 서브클래싱의 대안이 될 수 있습니다.
프로토타이핑은 다음과 같이 작동합니다. 일단, 다양한 방식으로 설정된 객체들의 집합을 만듭니다. 그 후 설정한 것과 비슷한 객체가 필요할 경우 처음부터 새 객체를 생성하는 대신 프로토타입을 복제하면 됩니다.
프로토타입 패턴은 복사해야 하는 객체들의 구상 클래스들에 코드가 의존하면 안 될 때 사용하세요.
프로토타입 패턴은 각자의 객체를 초기화하는 방식만 다른 자식 클래스들의 수를 줄이고 싶을 때 사용하세요.
부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴입니다.
팩토리 메서드는 당신의 코드가 함께 작동해야 하는 객체들의 정확한 유형들과 의존관계들을 미리 모르는 경우 사용하세요.
팩토리 메서드는 당신의 라이브러리 또는 프레임워크의 사용자들에게 내부 컴포넌트들을 확장하는 방법을 제공하고 싶을 때 사용하세요.
팩토리 메서드는 기존 객체들을 매번 재구축하는 대신 이들을 재사용하여 시스템 리소스를 절약하고 싶을 때 사용하세요.
추상 팩토리는 당신의 코드가 관련된 제품군의 다양한 패밀리들과 작동해야 하지만 해당 제품들의 구상 클래스들에 의존하고 싶지 않을 때 사용하세요. 왜냐하면 이러한 클래스들은 당신에게 미리 알려지지 않았을 수 있으며, 그 때문에 향후 확장성(extensibility)을 허용하기를 원할 수 있기 때문입니다.
클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근(액세스) 지점을 제공하는 생성 디자인 패턴입니다.
정부는 싱글턴 패턴의 훌륭한 예입니다. 국가는 하나의 공식 정부만 가질 수 있습니다. 그리고 'X의 정부'라는 명칭은 정부를 구성하는 개인들의 신원과 관계없이 정부 책임자들의 그룹을 식별하는 글로벌 접근 지점입니다.
싱글턴 클래스는 정적 메서드 getInstance를 선언합니다. 이 메서드는 자체 클래스의 같은 인스턴스를 반환합니다.
싱글턴의 생성자는 항상 클라이언트 코드에서부터 숨겨져야 합니다. getInstance 메서드를 호출하는 것이 Singleton 객체를 가져올 수 있는 유일한 방법이어야 합니다.
싱글턴 패턴은 당신 프로그램의 클래스에 모든 클라이언트가 사용할 수 있는 단일 인스턴스만 있어야 할 때 사용하세요. 예를 들자면 프로그램의 다른 부분들에서 공유되는 단일 데이터베이스 객체처럼 말입니다.
싱글턴 패턴은 전역 변수들을 더 엄격하게 제어해야 할 때 사용하세요.
큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층구조(추상화 및 구현)로 나눈 후 각각 독립적으로 개발할 수 있도록 하는 구조 디자인 패턴입니다.
기초 리모컨 클래스는 이 클래스를 장치 객체와 연결하는 참조 필드를 선언합니다. 모든 리모컨은 일반 장치 인터페이스를 통해 장치들과 작동하므로 같은 리모컨이 여러 장치 유형을 지원할 수 있습니다.
장치 클래스들과 독립적으로 리모컨 클래스들을 개발할 수 있으며, 필요한 것은 새로운 리모컨 자식 클래스를 만드는 것뿐입니다. 예를 들어 기초 리모컨에는 버튼이 두 개뿐일 수 있지만, 추가 터치스크린과 추가 배터리 같은 기능들도 가지도록 확장할 수 있습니다.
객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴입니다.
객체의 결합 을 통해 기능을 동적으로 유연하게 확장 할 수 있게 해주는 패턴
전화로 주문하기
전화로 주문하기 위해 매장에 전화를 걸었을 때 전화를 받는 교환원이 바로 상점의 모든 서비스와 부서에 대한 당신의 퍼사드입니다. 이때 교환원은 주문 시스템, 지불 게이트웨이 및 다양한 배송 서비스에 대한 간단한 음성 인터페이스를 제공합니다.
퍼사드 패턴을 사용하면 하위 시스템 기능들의 특정 부분에 편리하게 접근할 수 있습니다. 또 퍼사드는 클라이언트의 요청을 어디로 보내야 하는지와 움직이는 모든 부품을 어떻게 작동해야 하는지를 알고 있습니다.
추가적인 퍼사드 클래스를 생성하여 하나의 퍼사드를 관련 없는 기능들로 오염시켜 복잡한 구조로 만드는 것을 방지할 수 있습니다. 추가 퍼사드들은 클라이언트들과 다른 퍼사드들 모두에 사용할 수 있습니다.
복잡한 하위 시스템은 수십 개의 다양한 객체들로 구성됩니다. 이 모든 객체가 의미 있는 작업을 수행하도록 하려면, 하위 시스템의 세부적인 구현 정보를 깊이 있게 살펴야 합니다. 예를 들어 올바른 순서로 객체들을 초기화하고 그들에게 적절한 형식의 데이터를 제공하는 등의 작업을 수행해야 합니다.
하위 시스템 클래스들은 퍼사드의 존재를 인식하지 못합니다. 이들은 시스템 내에서 작동하며, 매개체 없이 직접 서로와 작업합니다.
클라이언트는 하위 시스템 객체들을 직접 호출하는 대신 퍼사드를 사용합니다.
퍼사드 패턴은 당신이 복잡한 하위 시스템에 대한 제한적이지만 간단한 인터페이스가 필요할 때 사용하세요.
퍼사드 패턴은 하위 시스템을 계층들로 구성하려는 경우 사용하세요.
플라이웨이트 패턴은 당신의 프로그램이 많은 수의 객체들을 지원해야 해서 사용할 수 있는 RAM을 거의 다 사용했을 때만 사용하세요.
이 패턴 적용의 혜택은 패턴을 사용하는 방법과 위치에 따라 크게 달라지며, 다음과 같은 경우에 가장 유용합니다.
프록시 패턴을 활용하는 방법들은 수십 가지가 있으며, 패턴이 가장 많이 사용되는 용도들을 살펴보겠습니다.
지연된 초기화(가상 프록시)
이것은 어쩌다 필요한 무거운 서비스 객체가 항상 가동되어 있어 시스템 자원들을 낭비하는 경우입니다.
앱이 시작될 때 객체를 생성하는 대신, 객체 초기화를 실제로 초기화가 필요한 시점까지 지연할 수 있습니다.
접근 제어 (보호 프록시)
당신이 특정 클라이언트들만 서비스 객체를 사용할 수 있도록 하려는 경우에 사용할 수 있습니다. 예를 들어 당신의 객체들이 운영 체제의 중요한 부분이고 클라이언트들이 다양한 실행된 응용 프로그램(악의적인 응용 프로그램 포함)인 경우입니다.
이 프록시는 클라이언트의 자격 증명이 어떤 정해진 기준과 일치하는 경우에만 서비스 객체에 요청을 전달할 수 있습니다.
원격 서비스의 로컬 실행 (원격 프록시)
서비스 객체가 원격 서버에 있는 경우입니다.
이 경우 프록시는 네트워크를 통해 클라이언트 요청을 전달하여 네트워크와의 작업의 모든 복잡한 세부 사항을 처리합니다.
요청들의 로깅(로깅 프록시)
서비스 객체에 대한 요청들의 기록을 유지하려는 경우입니다.
프록시는 각 요청을 서비스에 전달하기 전에 로깅(기록)할 수 있습니다.
요청 결과들의 캐싱(캐싱 프록시)
이것은 클라이언트 요청들의 결과들을 캐시하고 이 캐시들의 수명 주기를 관리해야 할 때, 특히 결과들이 상당히 큰 경우에 사용됩니다.
프록시는 항상 같은 결과를 생성하는 반복 요청들에 대해 캐싱을 구현할 수 있습니다. 프록시는 요청들의 매개변수들을 캐시 키들로 사용할 수 있습니다.
스마트 참조
이것은 사용하는 클라이언트들이 없어 거대한 객체를 해제할 수 있어야 할 때 사용됩니다.
프록시는 서비스 객체 또는 그 결과에 대한 참조를 얻은 클라이언트들을 추적할 수 있습니다. 때때로 프록시는 클라이언트들을 점검하여 클라이언트들이 여전히 활성 상태인지를 확인할 수 있습니다. 클라이언트 리스트가 비어 있으면 프록시는 해당 서비스 객체를 닫고 그에 해당하는 시스템 자원을 확보할 수 있습니다.
또 프록시는 클라이언트가 서비스 객체를 수정했는지도 추적할 수 있으며, 변경되지 않은 객체는 다른 클라이언트들이 재사용할 수 있습니다.
복합체 패턴은 객체들을 트리 구조들로 구성한 후, 이러한 구조들과 개별 객체들처럼 작업할 수 있도록 하는 구조 패턴입니다.
군대 구조의 예시
대부분의 국가에서 군대는 계층구조로 구성되어 있습니다. 군대는 여러 사단으로 구성되며, 사단은 여단의 집합이고, 여단은 소대의 집합이며, 소대는 또 분대로 나누어질 수 있습니다. 마지막으로 분대는 실제 군인들의 작은 집합입니다. 명령들은 계층구조의 최상위에서 내려와 모든 병사가 자신이 수행해야 할 작업을 알게 될 때까지 계층구조의 각 하위 계층으로 전달됩니다.
컴포넌트 인터페이스는 트리의 단순 요소들과 복잡한 요소들 모두에 공통적인 작업을 설명합니다.
잎은 트리의 기본 요소이며 하위요소가 없습니다.
일반적으로 잎 컴포넌트들은 작업을 위임할 하위요소가 없어서 대부분의 실제 작업들을 수행합니다.
컨테이너(일명 복합체)는 하위 요소들(잎 또는 기타 컨테이너)이 있는 요소입니다. 컨테이너는 자녀들의 구상 클래스들을 알지 못하며, 컴포넌트 인터페이스를 통해서만 모든 하위 요소들과 함께 작동합니다.
요청을 전달받으면 컨테이너는 작업을 하위 요소들에 위임하고 중간 결과들을 처리한 다음 최종 결과들을 클라이언트에 반환합니다.
클라이언트는 컴포넌트 인터페이스를 통해 모든 요소들과 작동합니다. 그 결과 클라이언트는 트리의 단순 요소들 또는 복잡한 요소들 모두에 대해 같은 방식으로 작업할 수 있습니다.
어댑터는 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴입니다.
어댑터 클래스는 기존 클래스를 사용하고 싶지만 그 인터페이스가 나머지 코드와 호환되지 않을 때 사용하세요.
이 패턴은 부모 클래스에 추가할 수 없는 어떤 공통 기능들이 없는 여러 기존 자식 클래스들을 재사용하려는 경우에 사용하세요.
중재자는 객체 간의 혼란스러운 의존 관계들을 줄일 수 있는 행동 디자인 패턴입니다. 이 패턴은 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 합니다.
반복자는 컬렉션의 요소들의 기본 표현(리스트, 스택, 트리 등)을 노출하지 않고 그들을 하나씩 순회할 수 있도록 하는 행동 디자인 패턴입니다.
반복자 패턴의 주 아이디어는 컬렉션의 순회 동작을 반복자라는 별도의 객체로 추출하는 것입니다.
반복자들은 다양한 순회 알고리즘들을 구현합니다. 여러 반복자 객체들이 동시에 같은 컬렉션을 순회할 수 있습니다.
반복자 객체는 알고리즘 자체를 구현하는 것 외에도 모든 순회 세부 정보들(예: 현재 위치 및 남은 요소들의 수)을 캡슐화하며, 이 때문에 여러 반복자들이 서로 독립적으로 동시에 같은 컬렉션을 통과할 수 있습니다.
반복자 패턴은 당신의 컬렉션이 내부에 복잡한 데이터 구조가 있지만 이 구조의 복잡성을 보안이나 편의상의 이유로 클라이언트들로부터 숨기고 싶을 때 사용하세요.
반복자는 복잡한 데이터 구조와 작업 시의 세부 사항을 캡슐화하여 클라이언트에 컬렉션 요소들에 접근할 수 있는 몇 가지 간단한 메서드들을 제공합니다. 이 접근 방식은 클라이언트에게 매우 편리합니다. 또 클라이언트가 컬렉션과 직접 작동할 때 클라이언트가 수행할 수 있는 부주의하거나 악의적인 행동들로부터 컬렉션을 보호합니다.
반복자 패턴을 사용하여 당신의 앱 전체에서 순회 코드의 중복을 줄이세요.
반복자 패턴은 코드가 다른 데이터 구조들을 순회할 수 있기를 원할 때 또는 이러한 구조들의 유형을 미리 알 수 없을 때 사용하세요.
옵서버 패턴은 당신이 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴입니다.
스마트폰의 버튼들과 스위치들은 장치의 현재 상태에 따라 다르게 행동합니다.
비지터(방문자) 패턴은 알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴입니다.
요청을 요청에 대한 모든 정보가 포함된 독립실행형 객체로 변환하는 행동 디자인 패턴입니다. 이 변환은 다양한 요청들이 있는 메서드들을 인수화 할 수 있도록 하며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 또 실행 취소할 수 있는 작업을 지원할 수 있도록 합니다.
당신은 도시를 한참 걷다가 멋진 레스토랑에 도착하여 창가 테이블에 앉습니다. 친절한 웨이터가 다가와 신속하게 당신의 주문을 받아 종이에 적습니다. 웨이터는 부엌으로 가서 주문을 벽에 붙입니다. 잠시 후 요리사에게 주문이 전달되고 요리사는 주문을 읽고 그에 따라 음식을 요리합니다. 요리사는 주문과 함께 식사를 트레이에 놓습니다. 웨이터는 트레이를 발견한 후 당신이 주문한 대로 식사가 요리되었는지 확인하고 완성된 주문을 당신의 테이블로 가져옵니다.
종이에 적힌 주문은 커맨드 역할을 합니다. 이 주문은 요리사가 요리할 준비가 될 때까지 대기열에 남아 있습니다. 주문에는 식사를 요리하는 데 필요한 모든 관련 정보가 포함되어 있습니다. 이를 통해 요리사는 당신에게서 주문 세부 사항을 직접 전달받는 대신 바로 요리를 시작할 수 있습니다.
작업들로 객체를 매개변수화하려는 경우 커맨드 패턴을 사용하세요.
커맨드 패턴은 작업들의 실행을 예약하거나, 작업들을 대기열에 넣거나 작업들을 원격으로 실행하려는 경우에 사용하세요.
커맨드 패턴은 되돌릴 수 있는 작업을 구현하려고 할 때 사용하세요.
알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다.
전략 패턴은 객체 내에서 한 알고리즘의 다양한 변형들을 사용하고 싶을 때, 그리고 런타임 중에 한 알고리즘에서 다른 알고리즘으로 전환하고 싶을 때 사용하세요.
전략 패턴은 일부 행동을 실행하는 방식에서만 차이가 있는 유사한 클래스들이 많은 경우에 사용하세요.
전략 패턴을 사용하여 클래스의 비즈니스 로직을 해당 로직의 콘텍스트에서 그리 중요하지 않을지도 모르는 알고리즘들의 구현 세부 사항들로부터 고립하세요.
이 패턴은 같은 알고리즘의 다른 변형들 사이를 전환하는 거대한 조건문이 당신의 클래스에 있는 경우에 사용하세요.
객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있게 해주는 행동 디자인 패턴입니다.
메멘토는 객체의 이전 상태를 복원할 수 있도록 객체의 상태의 스냅샷들을 생성하려는 경우에 사용하세요.
이 패턴은 또 객체의 필드들/게터들/세터들을 직접 접근하는 것이 해당 객체의 캡슐화를 위반할 때 사용하세요.
핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다. 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지를 결정합니다.
책임 연쇄 패턴은 당신의 프로그램이 다양한 방식으로 다양한 종류의 요청들을 처리할 것으로 예상되지만 정확한 요청 유형들과 순서들을 미리 알 수 없는 경우에 사용하세요.
이 패턴은 특정 순서로 여러 핸들러를 실행해야 할 때 사용하세요.
책임 연쇄 패턴은 핸들러들의 집합과 그들의 순서가 런타임에 변경되어야 할 때 사용하세요.
참고