디자인에 정답은 없지만 앞서 말한 원칙들이 잘 지켜지는 Best Practice들은 존재한다.
비슷한 문제에 이러한 BP들을 적용하여 쉽게 해결할 수 있으며,
복잡하게 디자인 구조를 설명할 필요 없이 패턴 이름만으로 의사소통과 설명이 가능하다.
State 패턴
- 한 객체가 상태에 따라 다르게 동작해야할 때 구현하는 패턴
예를들면 버튼 하나로 동작하는 선풍기가 있을 때
정지상태에서 버튼을 누르면 바람이 나와야하고, 바람이 나오는 상태에서 버튼을 누르면 정지해야한다.
사용자는 버튼 누르기 하나만 선택했는데, 선풍기 객체는 if문으로 현재 상태에 따라 동작이 바뀐다.
그럼 미풍,약풍,강풍 기능이 도입된다면?? if문에 3가지 분기가 추가된다.
이는 OCP에 어긋나는 내용으로 기능이 추가된다면, 기존의 코드를 수정하지 않아야한다.
또한 각 바람의 세기를 조절하려면 전부 선풍기 객체에서 찾아 변경해야한다.
그렇다면 사용자는 버튼 하나만 누르도록 요청하고, 선풍기가 인터페이스로 각 상태별로 기능을 다르게 구현해야한다.
단 요구하는 기능이 많아질 수록, 클래스도 많아진다는 단점이 있다.
Bridge 패턴
추상화와 구현을 분리하여 확장성과 유지보수성을 높인 디자인 패턴.
대표적으로 삼각형, 사각형으로 이루어진 모양과, 모양에 색칠을 할 색깔로 나눌 수 있다.
추상화 부분에 모양을 두고, 구현부에는 색깔을 두어 각자의 책임을 분리하고 기능이 추가될 때 독립적으로 확장이 가능하다.
추상화 (모양) 을 상속한 삼각형과 사각형은 색깔 인터페이스를 참조하여, 색칠 된 도형을 그린다.
모양이 추가되거나, 색깔이 추가되어도 각자에게는 영향이 가지 않아 유지보수성이 높다.
Adpater 패턴
흔히 해외여행에서 110v 짜리 전압을 사용하는 국가에 갈때 챙기는 돼지코(어댑터)를 생각하면 될 것 같다.
220V 전압을 사용하는 기기에 돼지코를 연결해, 110V에서도 사용할 수 있게 해주는 것이다.
이처럼 호환되지 않는 인터페이스를 가진 클래스들이 동작할 수 있게 해준다. ( 인터페이스 변환)
클라이언트 코드가 변경되지 않고, 다른 인터페이스를 사용할 수 있다는 장점이 있다.
돼지코 예시를 들면, 클라이언트가 원하는 서비스를 실제로 제공하는 220V 기기 ( Adaptee)와
그를 110V로 변환해주는 Adapter, 클라이언트가 사용하는 인터페이스인 110V로 구성된다.
Command 패턴
요청하는 Client 객체와 실제 작업을 수행하는 Receiver 객체의 결합성을 낮추기 위한 패턴.
요청을 객체화 하여 전달한다. 요청을 큐에 저장하거나 실행 취소, 재실행 등의 기능을 구현할 수 있다.
실제 그림은 복잡해보이지만, Client가 요청하고 Receiver가 실제로 요청을 수행한다.
과정을 보면 Client는 요청을 객체화 하여 Invoker에게 전달한다.
Invoker는 사전에 정의된 Command 객체를 지니고 있고 command 인터페이스로 execute 메소드를 호출한다
command 인터페이스를 구현된 ConcreteCommand 객체는 실제로 작업을 수행할 Receiver 객체를 가지고 있고, 이를 호출한다.
Receiver가 실제로 요청을 수행한다.
Visitor 패턴
객체의 구조와 수행되는 연산을 분리할 수 있는 패턴. 데이터 구조를 변경하지 않고도 새로운 연산을 추가할 수 있다.
연산 관련하여 Visitor 인터페이스를 통해 접근한다.
Visitor를 구현한 ConcreteVisitor 클래스에서 실제 작업이 수행되고,
객체 구조 관련한 인터페이스는 Element 인터페이스와 그 구현체에서 수행된다.
사용자는 Element 구현체를 통해 accept 메소드를 호출하여 Visitor 구현채의 visit 메소드를 수행한다.
Visit 연산에 대한 확장에는 자유롭지만, 객체 구조와 관련된 Element 확장 시에는 Visitor 클래스의 변경이 필요하다.
Proxy 패턴
실제 객체에 대한 접근을 제어하고 관리할 수 있는 패턴이다.
직접 접근을 막고 프록시 객체를 제공함으로써 지연 초기화 및 캐싱 기능을 제공할 수 있으며 보안처리도 가능하다.
※ 지연 초기화 : 객체의 필드 초기화 시점을 그 값이 처음 필요할 때까지 미루는 기법.
메소드로 분리하여 진짜 필요할 때만 수행한다.
사용자는 인터페이스로 작업을 요청하고, 내부 로직으로 프록시 객체에 도달한다.
프록시 객체는 실제 객체에게 작업을 수행시킬지 판단한다.
ex: 동일한 요청으로 캐싱값 가지고 있는 경우, 실제로 객체 초기화가 필요한지
프록시 객체는 실제 객체에게 작업을 요청하고 결과값을 반환받아 사용자에게 전달한다.
Decorator 패턴
객체에 추가적인 기능을 동적으로 추가할 수 있는 디자인 패턴이다.
기본적으로 데코레이터가 적용될 Componenet 객체가 존재하고, 이에 기능을 추가하는 Decorator로 나뉜다.
예를들어 크리스마스 트리를 꾸밀 때, 기본이 되는 실제 트리를 Component 객체라 할 수 있고
전구, 별 등의 장식품을 Decorator로 표현할 수 있다.
조합을 위해 수많은 자식 클래스가 생기지 않고, 동적으로 필요한 기능을 추가할 수 있다.
Coposite 패턴
객체를 트리 구조로 구성하여 개별/복합 개체를 동일하게 다룰 수 있는 패턴.
단일 객체인 Leaf와 단일,복합 개체를 포함하는 Compoiste 객체로 나뉜다.
단일/복합 객체를 구분할 필요가 없이 동일하게 취급하여 재귀적 구조를 쉽게 만드는 디자인 패턴이다.
윈도우의 파일 탐색기를 생각해보면 된다.
C드라이브 안에 폴더가 여러개 있고, 단일 수행파일이 있다. 폴더들은 또 트리형태로 폴더와 개별 파일들로 구성 되어있다.
Prototype 패턴
새로운 객체를 생성할 때 생성자가 아닌, 기존 객체의 복사본을 만들어 사용하는 패턴.
초기화 단계를 생략하고 현재 상태를 복사할 수 있다는 장점이 있지만, 객체마다 복사 시 예외처리들이 다 다르다는 단점.
COR ( Chain of Responsibility ) 패턴
요청을 보내는 클라이언트와 처리하는 객체(Handler) 사이의 결합도를 낮추는 패턴.
요청을 처리할 수 있는 여러 개의 Handler 객체를 생성하여 연결하고, Chain에 따라 순차적으로 처리한다.
Factory 패턴
객체의 생성과정을 캡슐화 하여, 클라이언트 코드와 분리하는 패턴.
클라이언트에서 New로 객체를 생성하지 않고, 팩토리 클래스에서 다양한 유형의 객체를 생성하여 리턴한다.
// Creator(팩토리) 인터페이스
interface Creator {
Product factoryMethod();
}
// ConcreteCreator(구체적인 팩토리) 클래스
class ConcreteCreatorA implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
// 다른 ConcreteCreator 클래스
class ConcreteCreatorB implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
// 클라이언트 코드
public class Main {
public static void main(String[] args) {
Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.factoryMethod();
Creator creatorB = new ConcreteCreatorB();
Product productB = creatorB.factoryMethod();
}
}
Abstract Factory 패턴
팩토리 패턴과 유사하나, 관련 객체들의 생성 들의 생성 시, 구상 클래스의 지정 없이 관련 객체들의 모음을 생성하는 패턴
유사한 객체들을 집단별로 찍어낼 수 있다는 점이 포인트다.
이를 위해 하나의 Factory에서 연관된 다양한 종류들의 객체를 찍어내야한다.
예를들어 의자, 소파, 커피테이블을 세트로 판매중이라면 생산시에 같은 디자인으로 만들어야 한다.
이를 위한 디자인 별로 추상 팩토리를 선언하고, 실제 의자, 테이블, 소파를 생산하는 팩토리를 구현한다.