최근 코딩을 하면서 ActionFactory를 다루게 되었습니다. 어렴풋이 알고 있던 디자인 패턴의 한 종류인 것은 알고 있었습니다만 역시 실제 이해하면서 다루게 되니 훨씬 재미있고, 복잡했던 인터페이스를 간소화할 수 있는 장점들이 눈에 띄었습니다. 생각난 김에 구글링을 통해서 기본적인 내용을 다루고, 코딩에서 사용한 디자인 패턴을 중점으로 하나씩 정리해보려 합니다. 먼저 디자인 패턴의 개요부터 시작하겠습니다.
1. 디자인 패턴 개요
위키의 한 줄 요약은 다음과 같습니다.
소프트웨어 디자인 패턴(software design pattern)
객체 지향 프로그래밍 설계를 할 때 공통적으로 발생하는 문제들에 대해 재사용 가능한 해결책이다.
여러 설명이 있습니다만 공통적인 내용은 디자인 패턴은 프로그램을 개발하는 과정에서 빈번하게 발생하는 디자인 문제를 정리해서 상황에 따라 간편하게 적용할 수 있게 일반화한 것입니다.
일반 프로그래머들이 매번 새롭게 설계하기보다는 이미 선대가 고민해놓은 디자인 패턴을 활용함으로써 보다 안정적이고 최적화하기 좋은 디자인 해결책으로 사용할 수 있습니다. 또한 여러 사람이 협업해서 개발할 때 소프트웨어의 설계는 매우 중요합니다. 여러 가지 설계 디자인이 나올 수 있지만 그 방법들을 일일이 설명하는 일은 아주 힘이 듭니다. 이때에 디자인 패턴은 의사소통의 수단으로써도 좋은 역할을 할 수 있습니다.
디자인 패턴이 소프트웨어 설계상의 모든 문제를 해결해주지 않습니다. 디자인보다 중요한 것은 코드베이스의 간결성입니다. 즉 디자인 패턴이 굳이 필요하지 않을 것 같은 부분은 하지 않는 것이 더 좋습니다.
디자인 패턴을 이용하려면 그 패턴의 효율성을 이해하고 내가 설계하는 프로그램에서 패턴이 유용한 해결책이라는 생각이 들 때 적용하는 것이 바람직합니다.
2. 디자인 패턴의 역사
디자인 패턴은 1987년에 작성된 "Using Pattern Languages for Object-Oriented Programs"를 통해 제안되었습니다.
이후 1995년에 이른바 사인방(Gang of Four)이 쓴 23개의 디자인 패턴을 수록한 "Design Patterns: Elements Of Reusable Object-Oritented Sotfware)라는 책이 출판되고 나면서 유명해 졌습니다.
현재에는 수천여 개의 패턴이 발표되어 있습니다.
3. 디자인 패턴 용도에 따라 나누기
디자인 패턴은 몇 가지 범주에 맞춰서 분류하고 있습니다.
제일 유명한 분류 방법은 GoF가 제시한 방법으로 크게 3가지로 나누었습니다.
- 생성(Creational) 패턴
- 구조(Structural) 패턴
- 행위(Behavioral) 패턴
3.1 생성(Creational) 패턴
객체 생성에 관련된 패턴입니다.
객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공합니다.
(1) 싱글턴 패턴 (Singleton Pattern)
특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해주는 패턴입니다.
싱글턴 패턴을 사용하면 전역 변수를 사용할 때와 마찬가지로 객체 인스턴스를 어디서든지 접근할 수 있게 만들 수 있습니다. 클래스 인스턴스를 하나만 만들고 그 인스턴스로 전력 접근을 제공합니다.
(2) 추상 팩토리 패턴 (Abstract Factory Pattern)
구체적인 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴입니다.
다양한 구성 요소 별로 '객체의 집합'을 생성해야 할 때 유용합니다. 이 패턴을 사용하여 상황에 알맞은 객체를 구성할 수 있습니다.
(3) 팩토리 메서드 패턴 (Fatory Method Pattern)
객체를 만들어 반환하는 함수를 생성자 대신 팩토리 형태로 제공하여 초기화 과정을 외부에서 보지 못하게 숨기고 반환 타입을 제어하는 방법입니다.
객체를 생성할 때 필요한 인터페이스를 만듭니다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메서드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 됩니다.
(4) 빌더 패턴 (Builder Pattern)
복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴입니다.
생성자를 통해 어떠한 값도 생성하지 않고 내부 클래스(Builder)를 통하여 생성하도록 하는 패턴입니다.
(5) 프로토타입 패턴 (Prototype Pattern)
기존의 객체를 복사하여 새로운 객체를 만들고 복사한 객체를 변경하여 사용하는 패턴입니다.
생성할 객체들의 타입이 프로토타입인 인스턴스로부터 결정되도록 하며, 인스턴스는 새 객체를 만들기 위해 자신을 복제(clone)하게 됩니다.
3.2 구조(Structural) 패턴
클래스나 객체를 조합해 더 큰 구조를 만드는 패턴입니다.
예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴입니다.
(1) 어댑터 패턴 (Adapter Pattern)
한 클래스의 인터페이스를 다른 클래스에서 사용하고자 할 때, 다른 클래스에서 사용할 수 있도록 호환성을 제공해주는 패턴입니다.
특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.
(2) 브리지 패턴 (Bridge Pattern)
구현부에 추상층을 분리하여 각자 독립적으로 변형할 수 있도록 하는 패턴입니다. 기능과 구현에 대해 두 개의 별도의 클래스로 구현하는 방법입니다.
(3) 컴포지트 패턴 (Composite Pattern)
객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체를 모두 동일하게 다루도록 하는 패턴입니다. 객체와 객체들의 집합 간의 처리 방법의 차이가 없을 경우 사용합니다.
객체를 트리구조로 구성해서 부분-전체 계층구조를 구현합니다. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있습니다.
(4) 데코레이터 패턴 (Decorator Pattern)
어떤 객체에 상황과 용도에 따라 새로운 책임을 추가하는 패턴입니다. 객체에 기능을 동적으로 추가하여 서브 클래스를 생성하는 것보다 유연하게 기능을 확장할 수 있습니다.
(5) 퍼사드 패턴 (Facade Pattern)
복잡한 호출 과정을 한 번 더 감싸서 단순한 형태로 제공하는 패턴입니다.
서브 시스템에 있는 일련의 인터페이스를 통합 인터페이스로 묶어줍니다. 또한 고수준 인터페이스도 정의하므로 서브시스템을 더 편리하게 사용할 수 있습니다.
(6) 플라이 웨이트 패턴 (Flyweight Pattern)
어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 '가상 인스턴스'를 제공하고 싶을 때 사용하는 패턴입니다. 인스턴스를 가능한 대로 공유하여 쓸데없이 new 연산자를 통한 메모리 낭비를 줄이는 방법입니다.
객체 생성을 관리하는 Fatory 클래스를 두고 생성한 객체를 Map을 통해 관리합니다. 객체 요청을 받아서 객체가 없으면 새로 생성한 다음 Map에 저장하고 존재하는 객체이면 Map에서 Instance를 꺼내어 반환합니다.
(7) 프락시 패턴 (Proxy Pattern)
의미 그대로 실제 기능을 수행하는 주체를 바로 호출하는 대신 대리자(Proxy)를 거쳐서 호출하는 방법입니다.
즉 클라이언트에서 실제 기능을 담당하는 객체가 아닌, 클라이언트에서 프락시 객체로, 프락시 객체에서 실제 기능을 담당하는 객체의 흐름으로 진행합니다.
3.3 행위 [ 행동 ] (Behavioral) 패턴
객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴입니다.
한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔 디자인 패턴입니다.
(1) 책임 연쇄 패턴 (Chain of responsibility Pattern)
명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴입니다.
클라이언트에게 어떠한 요청이 들어왔을 때, 요청을 받은 객체가 해당 요청을 해결할 수 없을 경우 연결된 다음 객체들에게 전달하고 해당 요청을 해결할 수 있는 객체가 처리하는 방식입니다. 요청 객체와 처리 객체를 분리하거나 요청을 처리할 수 있는 객체가 여러 개인데 하나의 객체에 요청을 보낼 때 책임 연쇄 패턴을 적용할 수 있습니다. 즉 요청을 처리할 수 있는 객체가 여러 개이고 이러한 처리를 하는 객체가 명시적이지 않을 때 사용할 수 있습니다.
각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들 할 수 없는 명령은 다음 처리 객체로 넘겨집니다. 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복됩니다.
(2) 커맨드 패턴 (Command Pattern)
특정 객체에 대한 커맨드를 객체화하여 커맨드 객체를 필요에 따라 처리하는 패턴입니다. 보통 주체 객체에서 대상 객체와 같은 방식으로 호출한다면 대상 객체에 대한 액션은 주체 객체에서 메서드로 처리하는 데 이 액션을 객체로 만들어 처리하는 방식입니다.
실행될 기능을 캡슐화함으로써 여러 기능을 실행할 수 있어 재사용성이 높습니다. 또한 기능이 수정되거나 변경이 일어났을 때, 주체 객체의 코드 수정 없이 기능에 대한 클래스만 정의하면 되어 확장성이 유연해집니다.
(3) 인터프리터 패턴 (Intepreter Pattern)
주어진 언어에 대해서 문법을 위한 표현 수단을 정의하고, 해당 언어로 된 문장을 해석하는 해석기를 사용하는 패턴입니다.
(4) 이터레이터 (반복자) 패턴 (Iterator Pattern)
컬렉션의 구현 방법을 노출시키지 않고 그 안에 존재하는 모든 항목에 접근할 수 있도록 하는 패턴입니다. 이 패턴을 사용하면 컬렉션 내에 구현이 어떤지 몰라도 해당 객체에 접근해 반복 작업을 처리할 수 있습니다.
(5) 중재자 패턴 (Mediator Pattern)
클래스 간의 복잡한 관계들을 캡슐화하여 하나의 클래스에서 관리하도록 처리하는 패턴입니다. M:N 관계를 이 패턴을 사용하여 M:1 관계로 만들어 복잡도를 내립니다.
M개의 객체들 사이에 중재자를 추가하여 중재자가 모든 객체들의 통신을 담당하도록 변경하면 중재자 패턴이라 볼 수 있습니다. 이와 같이 만들면 각 객체들은 서로 알 필요가 없고 중재자 클래스가 관리하므로 느슨한 결합(loose coupling)을 유지할 수 있어서 전체적인 흐름을 읽기 편해집니다. 다만 특정 application에 맞춰져서 개발이 되기 때문에 재사용이 어렵습니다.
(6) 메멘토 페턴 (Memento Pattern)
메멘토 패턴은 객체의 정보를 저장하고 사용자가 원하는 시점의 데이터를 복원할 수 있도록 해주는 패턴입니다.
메멘토 패턴은 오리지네이터(originator), 케어테이커(caretaker), 메멘토(memento)의 3개의 객체로 구현됩니다. 오리지네이터는 내부 상태를 보유하고 있는 일부 객체입니다. 케어테이커는 오리지네이터에 대해 무언가를 하지만 변경에 대한 실행 취소를 하기를 원합니다. 케어테이커는 먼저 오리지네이터에게 메멘토 객체를 요청하고 그 뒤 일련의 명령을 수행합니다. 명령 이전의 상태로 되돌리기 위해 메멘토 객체를 오리지네이터에 반환합니다.
(7) 옵서버 패턴 (Observer Pattern)
한 객체의 상태가 변경이 되면 해당 객체를 의존하고 있는 모든 객체에게 의존하고 있는 객체의 상태가 변경되었다고 알려주는 디자인 패턴입니다.
객체들 사이에 1:N의 의존관계를 정의하여 어떤 객체가 상태가 변할 때, 의존관계에 있는 모든 객체들이 통지받고 자동으로 갱신될 수 있게 만들어 줍니다. 즉 옵서버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵서버에게 통지하도록 하는 디자인 패턴입니다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용하며 발행/구독 모델로 알려져 있기도 합니다.
(8) 상태 패턴 (State Pattern)
객체 지향 방식으로 상태 기계(state machine)를 구현하는 디자인 패턴입니다. 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다. 즉 어떤 행동을 수행할 때, 상태에 맞는 행동을 수행하도록 처리합니다.
(9) 전략 패턴 (Strategy Pattern) = 정책 패턴 (Policy Pattern)
실행 중에 알고리즘을 선택할 수 있게 하는 디자인 패턴입니다.
동일 계열의 알고리즘들을 정의하고, 각각 캡슐화하며 이들을 상호 교환 가능하도록 만드는 것입니다. 알고리즘을 사용하는 사용자로부터 독립적으로 알고리즘이 변경될 수 있도록 하는 패턴입니다.
알고리즘 군을 정의하고 캡슐화해서 각각의 알고리즘 군을 수정해서 쓸 수 있게 해 줍니다. 이 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
(10) 템플릿 메서드 패턴 (Template Method Pattern)
여러 클래스에서 공통적으로 호출, 사용하는 메서드들을 상위 클래스에서 정의하고 이 상위 클래스를 상속받은 하위 클래스에서 세부 동작을 구현하는 패턴입니다.
객체의 연산에서 알고리즘의 뼈대만 정의하고, 나머지는 서브클래스에서 이루어지게 하는 패턴으로 알고리즘의 구조는 변경하지 않고 알고리즘의 각 단계를 서브클래스에서 재정의하게 됩니다. 이 패턴을 사용하면 중복 코드를 제거할 수 있고 상속을 받은 하위 클래스의 역할이 줄어 로직 관리가 편리합니다. 하지만 추상 메서드가 많아진다면 클래스의 관리가 복잡해진다는 단점이 있습니다.
(11) 방문자 패턴 (Visitor Pattern)
수행 로직을 객체 구조에서 분리하는 디자인 패턴으로 비슷한 종류의 객체들을 가진 그룹에서 작업을 수행해야 할 때 주로 사용하는 패턴입니다.
방문자와 방문 공간을 분리하여 방문 공간이 방문자를 맞이할 때, 이후에 대한 행동을 방문자에게 위임하는 패턴입니다. 비슷한 객체에서 어떤 동작을 해야 할 때 방문자 패턴을 사용하면 수행 로직을 분리할 수 있습니다. 즉 데이터 구조와 연산을 분리하여 구조를 변경하지 않고 새로운 연산을 추가할 수 있습니다. 새로운 연산을 추가하고 싶으면 새로운 방문자를 추가하면 되는 형식입니다.
참고
한빛 출판네트워크 : [Design pattern] 많이 쓰이는 14가지 핵심 GoF 디자인 패턴의 종류
https://en.wikipedia.org/wiki/Design_Patterns
'Dev. Handbook > Tech, Trend' 카테고리의 다른 글
Web Server, WAS, CGI, Web Container, Java SE, Java EE, Jakarta EE (0) | 2022.05.29 |
---|---|
Declarative,선언형 vs Imperative,명령형 프로그래밍 (0) | 2022.05.01 |
댓글