본문 바로가기

Design/Design Pattern

[Design Pattern] 정리

지금까지 GoF의 디자인 패턴에 나와있는 23가지의 디자인 패턴들에 대해서 알아보았다.
이번 장에서는 지금까지 알아본 디자인 패턴들을 정리해보도록 한다.


GoF 디자인 패턴에서는 총 세개의 유형으로 디자인 패턴들을 정리하였다.
각 패턴에 필자가 작성한 글의 링크를 걸어두었다.

  1. 생성 패턴(Creational Pattern):
    객체의 생성과 관련있다. 생성 패턴 대부분은 정확히 무엇을 생성하는지에 대한 정보 없이도 객체를 생성할 수 있도록 해준다.
  2. 구조 패턴(Structural Pattern):
    프로그램의 구성에 관련있다. 구조 패턴은 프로그램 조직화에 대한 목적을 달성한다.
  3. 행동 패턴(Behavioral Pattern):
    객체 간의 행위에 관련있다. 객체의 역할과 객체들간의 상호 작용하는 방식을 정의한다.

지금부터 하나씩 정의, 장단점에 대해서 알아보도록 한다.
필자가 정리를 하는 목표는 추후 면접 또는 인터뷰를 보기 직전에 디자인 패턴을 한번 훑어보는 용도로 사용하기 위함이다.
만약 위에 나열되어있는 디자인 패턴들이 친숙하지 않다면 이해하기 힘들 수 있다.

  1. 팩토리 메서드 패턴(Factory Method Pattern)
    • 정의: 객체를 생성하는 Factory를 만들고 어떤 객체를 만들지는 자식 클래스에게 위임하는 패턴.
    • 장점:
      • 객체가 생성되는 곳이 한 곳에 집중되기 때문에 유지보수가 편하다.
      • 객체를 사용하는 코드에서 구체화된 객체가 아니라 추상화에의 의존하므로 DIP에 위배되지 않는 코드를 만들 수 있다. (객체간 결합도가 낮아진다.)
    • 단점:
      • 하위 클래스를 생성하는 팩토리 클래스가 수없이 많아질 수 있다.
        즉, 상위 클래스를 확장(상속)한 하위 클래스가 많은 경우 맞지 않는 패턴이다.
    • 필자의 의견:
      • 수없이 많은 곳에서 new를 통하여 객체 생성이 이루어지고 유지보수가 어려워져 리팩토링을 하거나 이러한 상황을 방지하기 위해 사용.
      • 객체를 사용하는 쪽에서 구체화된 객체에 의존하여 추후 객체가 변경될 때 사용하는 쪽의 코드가 변경되는 것을 방지하기 위하여 사용.
  2. 추상 팩토리 패턴(Abstract Factory Pattern)
    • 정의: 객체들의 집합(완제품)객체를 생성할 때 부품 객체까지 한 번에 조립해서 생성하는 패턴.
    • 장점:
      • 객체들의 집합(완제품)이 생성되는 과정과 책임을 캡슐화하여 문제가 발생하였을 때 하나의 관리포인트만 확인하면 된다.
      • 객체들의 집합(완제품)을 찍어내는 팩토리를 변경하는 것만으로 구체화된 객체의 변경이 가능하다. DIP에 위배되지 않는 코드를 만들 수 있다.
    • 단점:
      • 새로운 완제품이 필요하다면 새로운 팩토리 클래스가 필요하다.
        즉, 완제품이 종류가 수없이 많은 경우라면 적절하지 않은 패턴이다.
    • 필자의 의견:
      • 객체들의 집합(완제품)의 조합이 많지 않으며 자주 변경되지 않는 경우에 사용.
  3. 빌더 패턴(Builder Pattern)
    • 정의: 객체를 생성하는 Builder 클래스에게 객체 생성을 위임하는 패턴.
    • 장점:
      • 매개변수의 갯수와 상관없이 필요한 값만 가지는 객체를 생성할 수 있다.
      • 가독성이 좋아진다.
    • 단점:
      • 실제 객체의 클래스를 제외하고 Builder 클래스 또한 관리를 해야한다.(Lombok을 사용한다면 단점이 아니다.)
    • 필자의 의견:
      • Spring 개발자들은 @Builder 어노테이션을 통해서 고민없이 사용하자. 어짜피 Lombok이 모든 것을 해결해준다.
  4. 프로토 타입 패턴(Prototype Pattern)
    • 정의: 객체를 생성할 때 프로토타입 객체를 복사하여 생성하는 패턴.
    • 장점:
      • 객체를 생성하는 비용이 비싼 객체 생성을 복사를 통해 간소화할 수 있다.
    • 단점:
      • 프로토타입을 적용한 클래스들은 Cronable을 구현해야한다.
    • 필자의 의견:
      • 객체를 생성하는 비용(리소스 사용, 생성 시간)이 비싼 객체들을 관리할 때 사용.
  5. 싱글톤 패턴(Singleton Pattern)
    • 정의: 어떠한 클래스의 인스턴스가 단 하나임을 보장하는 패턴.
    • 장점:
      • 단 하나의 인스턴스만 생성되므로 메모리 사용량을 줄일 수 있다.
      • 생성되어 있는 인스턴스를 사용하므로 생성에 필요한 시간을 줄일 수 있다.
    • 단점:
      • 싱글톤 인스턴스의 경우 private이기 때문에 상속이 불가능하다.
      • 다중 Thread의 경합에 대비해야한다.
      • 분산 서버의 경우 한 개의 인스턴스만 생성됨을 보장할 수 없다.
    • 필자의 의견:
      • 한 개가 생성되던 몇 개가 생성되던 서비스 로직에 영향이 없으며 단지 메모리 사용량과 인스턴스 생성의 시간을 단축하기 위한 용도로만 사용.
      • 다중 Thread의 경합에 대비해야하여 싱글톤 인스턴스의 모든 로직에 synchronized가 사용되어야 한다면 사용하지 않는다.
  6. 어댑터 패턴(Adapter Pattern)
    • 정의: 호환성이 없는 기능을 사용하기 위해 중간에 호환을 맞춰주는 클래스를 두는 패턴.
    • 장점:
      • 기존의 코드를 변경하지 않고 중간에 어댑터를 두는 것만으로 해결된다.
      • 기존 코드를 변경하지 않기 때문에 사이드 이펙트를 줄일 수 있으며 재사용성이 증가한다.
    • 단점:
      • 필자의 생각엔 단점이 없다. 굳이 뽑자면 "문제가 발생하였을 때 어댑터 클래스와 로직이 있는 클래스 둘을 확인해야한다." 정도 되겠다.
    • 필자의 의견:
      • 사내에서 사용하고 있는 공통 모듈이 있고 모든 부서에서 사용하고 있는 공통 모듈이라 수정이 불가능할 때 중간에 어댑터를 두어 공통 모듈의 수정없이 사용.
  7. 브릿지 패턴(Bridge Pattern)
    • 정의: 구현과 추상화된 부분으로 분리하여 독립적으로 관리하는 패턴.
    • 장점:
      • 구현과 추상화된 부분이 독립되어 있기 때문에 코드가 유연해지며 재사용성이 증가한다.
      • 독립적으로 테스트가 가능하다.
    • 단점:
      • 없다.
    • 필자의 의견:
      • 변경되지 않는 부분을 구현부에 두고 실시간으로 변경 가능한 부분을 추상화된 부분으로 분리하여 필요할 때마다 바꿔서 사용하는 방식으로 사용.
        예를들어 지도 관련 기능을 개발할 때 내가 개발한 기능은 구현부에 두고 외부 API(TMap, DaumMap)를 사용하는 부분을 추상화하여
        TMap에서 일일 사용가능한 한도가 초과하면 DaumMap API를 호출하는 부분으로 변경하여 사용하도록 사용.
  8. 컴포지트 패턴(Composite Pattern)
    • 정의: 전체의 계층을 하나의 인터페이스로 통합하여 트리구조로 구성하는 패턴.
    • 장점:
      • 객체들이 같은 인터페이스를 구현한 타입으로 취급되기 때문에 코드가 단순해진다.
      • 새로운 계층이나 구성요소를 쉽게 추가할 수 있다.
    • 단점:
      • 구조가 지나치게 범용적이기 때문에 구성요소에 제약을 두기 힘들다.
    • 필자의 의견:
      • 없음. (객체를 계층 구조로 설계해야할 때 사용하면 좋을듯 한데 적용가능한 구체적인 사용처가 떠오르지 않음)
  9. 데코레이터 패턴(Decorator Pattern)
    • 정의: 상황에 따라 객체의 책임과 기능을 동적으로 추가하는 패턴.
    • 장점:
      • 기존 코드의 수정없이 데코레이터들을 통해 쉽게 기능 확장이 가능하다.
      • 실행 중에 새로운 책임과 기능을 추가할 수 있다.
    • 단점:
      • 수없이 많은 데코레이터 클래스가 추가될 수 있다.
      • 한 번에 여러 데코레이터가 추가되면 코드의 가독성이 떨어질 수 있다.
    • 필자의 의견:
      • 기존 객체의 기능 확장이 필요하여 하위 클래스가 필요한 경우 하위 클래스를 만드는 것이 아닌 기존 클래스에 데코레이터를 추가하여 사용.
      • 여러 요소들을 조합해서 사용하는 클래스 구조인 경우에 사용.
  10. 퍼사드 패턴(Facade Pattern)
    • 정의: 복잡한 작업을 수행할 때 이를 호출하는 쪽에게 복잡함을 숨기고 간단한 인터페이스를 제공하는 패턴.
    • 장점:
      • 클라이언트가 복잡한 로직을 직접 순차적으로 호출하여 발생하는 오류를 막을 수 있다.
      • 라이브러리나 서브 시스템과 이를 호출하는 클라이언트 코드간의 의존성을 줄일 수 있다. (개발할 때 유연성이 향상된다.)
    • 단점:
      • 없다.
    • 필자의 의견:
      • 자주 사용법이 어떤 객체를 사용할 때 이를 의존하는 객체들까지 복잡해지는 경우가 있다. 이러한 경우 복잡함을 퍼사드 객체로 몰아넣고 다른 객체들은 깔끔하게 유지하는데 사용.
  11. 플라이웨이트 패턴(Flyweight Pattern)
    • 정의: 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하도록 하여 메모리 사용량을 최소화하는 패턴.
    • 장점:
      • 많은 객체를 생성할 때 자원 사용 측면에서 이득을 볼 수 있다.
      • 한 번의 수정으로 모든 객체에 공유되는 데이터를 변경할 수 있다.
    • 단점:
      • 특정 객체에서만 공유되는 데이터를 변경하고 싶더라도 불가능하다.
        • 필자의 의견:
      • 객체별로 고유하지 않으며 공유 가능한 값이라면 싱글톤 인스턴스로 생성하고 객체간 공유하도록 사용.
  12. 프록시 패턴(Proxy Pattern)
    • 정의: 객체들 간에 직접적인 의존이 아닌 중간에 프록시 객체를 두고 프록시 객체에 의존하게 하는 패턴.
    • 장점:
      • 가상프록시의 경우 데이터 초기화를 늦추어 필요한 시점에 데이터를 로드하여 리소스 사용 측면에서 이득을 볼 수 있다.
      • 보호프록시의 경우 부가적인 기능을 프록시 객체로 몰아넣고 기존 객체는 로직만 관리하도록 할 수 있다.
    • 단점:
      • 프록시 객체가 부가적으로 생성되므로 객체 생성이 빈번하게 일어난다면 성능이 저하된다.
    • 필자의 의견:
      • 부가적인 기능(예를들어 로그 출력)으로 인해 서비스 로직(주로직)을 구현하는 코드가 지저분해지는 경우가 많은데 이러한 경우 부가적인 기능을 주로직으로 부터 분리하도록 사용.
  13. 인터프리터 패턴(Interpreter Pattern)
    • 정의: 주어진 언어에 대해서 문법을 위한 표현수단을 정의하고 해당 언어로 된 문장을 해석하는 패턴.
    • 장점: 생략.
    • 단점: 생략.
    • 필자의 의견: 생략.
  14. 템플릿 메서드 패턴(Template Method Pattern)
    • 정의: 특정 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체적인 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴.
    • 장점:
      • 상위 클래스의 내용은 하위 클래스에서 공통적으로 사용하므로 중복코드를 줄일 수 있다.
      • 패턴을 사용하기 전보다 객체지향적으로 구성할 수 있다.
    • 단점:
      • 하위 클래스가 구현해야하는 추상 메서드가 많아지면 관리가 힘들어진다.
      • 상위 클래스에서 하위 클래스의 메서드를 호출하는 방식으로 로직이 구성되야 하는데 이를 잘못 구현한다면 코드가 꼬일 수 있다.
    • 필자의 의견:
      • 전략 패턴과 비슷한 구조다. 다만 전략 패턴은 상황에 맞는 전략을 사용하는 패턴이라면 템플릿 메서드 패턴은 공통된 부분을 묶고 특정 부분만 고유하게 구현하는 패턴으로 둘이 지향하는 바가 다르므로 적절한 곳에 사용해야한다.
  15. 책임 사슬 패턴(Chain of Responsibility Pattern)
    • 정의: 하나의 요청을 여러 객체에게 처리할 수 있도록 구성한 패턴.
    • 장점:
      • 사슬에 들어가는 객체의 순서를 동적으로 추가 및 제거할 수 있다.
      • 클라이언트는 책임 사슬이 어떠한 구조로 되어 있는 알 필요가 없다.
    • 단점:
      • 책임 사슬 중 어떤 객체가 처리한 것인지 디버깅이 힘들 수 있다.
      • 모든 사슬을 통과하더라도 요청이 처리되지 않을 수 있다.
    • 필자의 의견:
      • 개발하다보면 만약 실패하면 이렇게. 또 실패하면 이렇게 이러한 식으로 되어 있는 코드를 자주본다. 이러한 코드를 만난다면 책임 사슬 패턴을 적용해본다.
  16. 이터레이터 패턴(Iterator Pattern)
    • 정의: Collection의 구현체를 노출시키지 않고 내용물에 접근할 수 있는 패턴.
    • 장점:
      • 클라이언트는 내용물이 어떠한 Collection으로 구현되어 있는지 알 필요가 없이 같은 접근 방식으로 접근이 가능하다.
    • 단점:
      • 이터레이터 패턴을 구현하는 클래스가 많아지면 구조가 복잡해질 수 있다.
    • 필자의 의견:
      • Collection에 특화된 어댑터 패턴으로 생각이 된다. 대부분 어댑터 패턴으로 구현하고 Collection에 어댑터가 필요한 경우에 사용한다.
  17. 미디에이터 패턴(Mediator Pattern)
    • 정의: 객체간의 상호작용이 복잡한 경우 중재자를 통해 객체들을 상호작용 하도록 하는 패턴.
    • 장점:
      • 객체간 상호작용을 위해 서로를 의존할 필요가 없어진다.
      • 상호작용하는 객체들의 코드 수정없이 의존 관계를 수정할 수 있다.
      • 중재자 객체 확인만으로 서비스의 전반적인 흐름을 이해하기 쉽다.
    • 단점:
      • 서비스가 커질수록 중재자 객체가 커져서 중재자 객체 자체의 유지보수가 어려워질 수 있다.
      • 특정 서비스에 종속되기 때문에 추후 다른 서비스에서 사용하기 어렵다.
    • 필자의 의견: 생략.
  18. 메멘토 패턴(Memento Pattern)
    • 정의: 객체를 이전 상태로 복구시켜야 하는 경우에 사용한다.
    • 장점:
      • 별도의 객체로 상태를 저장하기 때문에 안전하다.
      • 복구 기능을 구현하기 쉽다.
    • 단점:
      • 없음. (물론 필자의 생각에)
    • 필자의 의견:
      • Database의 경우 Transaction이 실패하는 경우 실제로 저장되어 있는 데이터는 변경되지 않는다. 하지만 우리가 생성한 객체의 데이터는 변경 될 수 있다. Database의 Transaction이 시작하기 전의 객체 상태를 저장해두고 혹시라도 Transaction이 commit되지 못하는 경우 객체의 상태도 이전으로 돌려서 Database에 저장되어 있는 데이터와 동일하게 유지하도록 사용한다.
  19. 옵저버 패턴(Observer Pattern)
    • 정의: 객체의 상태를 관찰하는 옵저버를 등록하고 변화가 생길 때마다 옵저버에게 통지하도록 하는 패턴.
    • 장점:
      • 옵저버와 관찰당하는 대상이 느슨하게 결합되어 언제든지 추가 삭제가 가능하다.
    • 단점:
      • 옵저버에게 통지하는 코드(부수적인 코드)가 주로직에 포함되어야 한다. (물론 제거 가능하지만 이 또한 유지보수를 해주어야한다.)
    • 필자의 의견:
      • 특별한 의견은 없다. 목적이 분명한 패턴으로 필요한 상황이 생긴다면 사용한다. Pull & Push 방식이 존재하므로 사용할 때 더 적절한 방법이 무엇인지 고민해보고 사용하도록 한다.
  20. 상태 패턴(State Pattern)
    • 정의: 어떠한 객체가 자신의 상태값을 변경하는 책임을 상태 객체에게 위임하는 패턴.
    • 장점:
      • 상태에 따른 분기문이 제거되고 가독성이 높아진다.
    • 단점:
      • 상태만큼의 클래스가 생성된다.
    • 필자의 의견:
      • 단점때문에 상태를 변경시키는 로직이 상당히 복잡하지 않는 이상 독립적인 사용은 힘들듯하다. 상태 클래스를 여러 클래스에서 사용할 수 있게 설계할 수 있다면 도입을 고려해볼만한 패턴.
  21. 전략 패턴(Strategy Pattern)
    • 정의: 어플리케이션이 실행되는 도중에 알고리즘을 선택할 수 있게 하는 패턴.
    • 장점:
      • 기존 코드의 변경없이 새로운 전략을 추가할 수 있다.
    • 단점:
      • 새로운 요구사항이 추가되었을 때 새로운 클래스를 추가하여 전략 클래스를 추가해야한다.
    • 필자의 의견:
      • 분기처리를 할 것인가? 전략 패턴을 사용할 것인가?를 고민하여 도입을 결정해야한다. 전략이 많지 않다면 분기처리가 오히려 가독성이 높을 수 있다.
  22. 방문자 패턴(Visitor Pattern)
    • 정의: 로직과 구조를 분리하여 새로운 로직이 추가될 때 구조를 변경하지 않고 추가할 수 있도록 하는 패턴.
    • 장점:
      • 구조의 변경없이 새로운 로직을 추가할 수 있다.
      • 로직 클래스들은 동일한 인터페이스를 구현하므로 사용법이 동일하다.
    • 단점:
      • 새로운 로직이 추가될 때 기존 코드의 변경은 필요없다. 하지만 새로운 구조가 생긴다면 모든 로직 코드에 변경이 필요하다.
    • 필자의 의견:
      • 전략패턴과 비슷하게 느껴질 수 있다. 전략 패턴은 사용하려는 알고리즘을 클라이언트에서 선택하는 것이며 방문자 패턴은 방문자의 종류에 따라 로직이 변경되는 것이다. 둘의 차이를 명확히 알고 사용.
  23. 커맨드 패턴(Command Pattern)
    • 정의: 특정 객체에 대한 요청을 캡슐화하는 패턴.
    • 장점:
      • 작업을 요청하는 클래스와 수행하는 클래스를 분리할 수 있다.
      • 새로운 커맨드가 추가될 때 기존의 코드는 수정될 필요가 없다.
    • 단점:
      • 작업이 추가될 때 새로운 클래스가 추가되야한다.(대부분의 행동 패턴의 단점인듯)
    • 필자의 의견: 없음.

'Design > Design Pattern' 카테고리의 다른 글

[Design Pattern] Proxy Pattern  (0) 2022.02.06
[Design Pattern] Interpreter Pattern  (0) 2022.02.06
[Design Pattern] Command Pattern  (0) 2022.02.06
[Design Pattern] Iterator Pattern  (0) 2022.02.04
[Design Pattern] Flyweight Pattern  (0) 2022.02.04