소프트웨어 디자인 패턴

디자인 패턴이란?

  • 디자인 패턴은 소프트웨어 설계에서 흔히 발생하는 문제에 대한 해결책이다.
  • 패턴은 특정 코드가 아니라 특정 문제를 해결하기 위한 일반적인 개념이다.

우리가 디자인 패턴을 배워야(알아야) 하는 이유

  • 디자인 패턴은 소프트웨어 설계에서 공통적인 문제에 대한 시도되고 테스트된 솔루션의 툴킷이다.
  • 이런 문제를 만나지 못하더라도, 패턴을 아는 것은 객체 지향적인 디자인의 원리를 사용하여 모든 종류의 문제를 해결하는 방법을 가르쳐주기 때문에 유용하다.
  • 디자인 패턴은 동료들과 더 효율적으로 의사소통하기 위해 사용할 수 있는 공통 언어를 정의한다.

디자인패턴 종류

생성 패턴(Creational Patterns)

객체 생성에 관련된 패턴이다. 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다.

  1. 싱글톤 패턴(Singleton) : 클래스의 인스턴스가 하나임을 보장하고 접근할 수 있는 글로벌 엑세스 포인트를 제공하는 패턴이다.

    • 프로그램의 클래스에 모든 클라이언트가 사용할 수 있는 단일 인스턴스가 있어야 하는 경우에 사용한다.
    • ex) 프로그램의 다른 부분에서 공유하는 단일 데이터베이스 객체
    • 글로벌 변수에 대해 엄격한 제어가 필요할 때 사용한다.
    • 장점
    • 클래스에 하나의 인스턴스만 있다고 확신할 수 있다.
    • 싱글톤 객체는 처음 요청될 때만 초기화된다.
    • 단점
    • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유할 경우 다른 클래스의 인스턴스들 간에 결합도가 높아져서 개방 폐쇄 원칙을 위반하게 된다.
    • 테스트가 어려워 질 수 있다.
    • 멀티스레드 환경에서 특별한 처리가 필요하다.

      // JVM의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다.
      
      // static이기 때문에 클래스 로딩시점에 한 번만 호출된다.
      // final을 사용해 다시 값이 할당되지 않도록 한다.
      public class ExampleClass {
       
      	private ExClass () {}
      	private static class Singleton {
       	private static final ExClass instance = new ExClass();
      	}
      	
      	public static ExClass getInstance () {
      		return Singleton.instance;
      	}
      }
  2. 추상팩토리 패턴(Abstract Factory) : 구체적인 클래스를 지정하지 않고 관련성이 있거나, 독립적인 객체들을 생성하기 위한 인터페이스를 제공하는 패턴이다.

    • 구체적인 클래스를 지정하지 않고 관련 객체의 집합을 생성할 수 있는 창조적 설계 패턴이다.
    • 코드가 여러 관련 제품군과 함께 작동해야 하지만 다양한 제품의 구체적인 종류에 따라 코드가 달라지는 것을 원하지 않을 때 사용한다.
    • 장점
    • factory 에서 생산하는 제품이 서로 호환되는지 알 수 있다.
    • 제품과 클라이언트 코드 간의 긴밀한 결합을 피할 수 있다.
    • 단일 책임 원칙을 준수한다.
    • 개방 폐쇄 원칙을 준수한다. 기존 클라이언트 코드를 깨지 않고 새로운 변형 제품을 도입할 수 있다.
  3. 빌더 패턴(Builder) : 복잡한 객체를 단계 별로 구성할 수 있게 해주는 패턴이다.

    • 번거로운 생성자 작업을 줄여준다.
    • 제품의 다양한 표현의 생산에서 세부사항만 다른 유사한 단계가 있을 때 사용한다.
    • composite 트리나 다른 복합적인 객체들을 생성하는 데에 사용한다.
    • 장점
    • 동일한 생산 코드를 재사용할 수 있다.
    • 단일 책임 원칙을 준수한다. 복잡한 코드를 제품의 비즈니스 로직에서 분리할 수 있다.
    • 단점
    • 새로운 클래스를 여러 개 만들어야하기 때문 코드의 전반적인 복잡성이 증가한다.
    • +) Lombok의 @Builder 어노테이션을 사용하면 체인 구조로 코드를 작성할 수 있다. (빌더 패턴 적용) -> immutable 객체를 생성할 수 있다.
    @Builder
    public class User {
      private String name;
      private Integer age;
      private String phoneNumber;
    }
    User user = User.builder()
      .name("뮹화니")
      .age(29)
      .phoneNumber("비밀")
      .build();
  4. 팩토리 메서드 패턴(Factory Method) : 객체를 생성하는 인터페이스를 정의하지만, 인스턴스를 만드는 클래스는 서브클래스에서 결정하도록 하는 패턴이다. 팩토리 메서드에서는 인스턴스를 만드는 것을 서브 클래스에서 하게 된다.

    • 객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 한다.
    • 사용할 객체의 정확한 타입과 의존성을 모를 때 사용한다.
    • 라이브러리나 프레임워크 사용자에게 내부 구성요소를 확장할 수 있는 방법을 제공하고 싶을 때 사용한다.
    • 기존 객체를 재구성하는 대신 재사용하여 시스템 리소스를 절약하고 싶을 때 사용한다.
    • 장점
    • 긴밀한 결합을 피할 수 있다.
    • 단일 책임 원칙을 준수한다.
    • 개방 폐쇄 원칙을 준수한다.
    • 단점
    • 패턴을 구현하기 위한 많은 새로운 하위 클래스를 도입해야할 수도 있기 때문에 코드가 복잡해질 수 있다.
    많은 설계가 팩토리 메소드 패턴을 사용하는 것으로 시작하여 추상 팩토리 패턴, 프로토타입 패턴, 빌더 패턴으로 발전한다.
  5. 원형 패턴(Prototype) : 생성할 객체의 종류를 명시하는 데 원형이 되는 예시물을 이용하고 새로운 객체를 이 원형들을 복사함으로써 생성하는 패턴이다.

    • 복사해야 할 객체의 구체적인 클래스에 의존하지 않아야 하는 경우 사용한다.
    • 각 객체를 초기화하는 방식이 다른 하위 클래스들의 수를 줄일 때 사용한다.
    • 장점
    • 구체 클래스에 연결하지 않고 객체를 복제할 수 있다.
    • 반복적인 초기화 코드를 제거할 수 있다.
    • 복잡한 객체를 편리하게 생산할 수 있다.
    • 복잡한 객체에 대한 구성 프리셋을 처리할 때 상속 대신 사용할 수 있다.
    • 단점
    • 순환 참조를 가지는 복잡한 객체를 복제하는 것은 까다로울 수도 있다.

      순환 참조 : A -> B -> C -> A 같이 꼬리를 무는 형태의 참조

구조 패턴(Structural Patterns)

클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다. 예를 들어, 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.

  1. 어댑터 패턴(Adapter aka Wrapper) : 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해주는 패턴이다.
  2. 브릿지 패턴(Bridge) : 구현부에 추상층을 분리하여 각자 독립적으로 변형할 수 있도록 하는 패턴이다.
  3. 컴포지트 패턴(Composite) : 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 / 복합객체 모두 동일하게 다루도록 하는 패턴이다.
  4. 데코레이터 패턴(Decorator) : 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로, 기능 확장이 필요할 때 서브클래스 대신 쓸 수 있는 대안이 될 수 있다.
  5. 퍼사드 패턴(Facade) : 서브시스템에 있는 인터페이스 집합에 통합된 하나의 인터페이스를 제공합니다. 서브시스템을 좀 더 쉽게 사용하기 위해 고수준의 인터페이스를 정의한다.
  6. 플라이웨이트 패턴(Flyweight) : 각 객체에 모든 데이터를 보관하는 대신 여러 객체 간에 공통적인 상태 부분을 공유하여 사용 가능한 RAM에 더 많은 객체를 넣을 수 있는 구조 설계 패턴이다.
  7. 프록시 패턴(Proxy) : 어떤 다른 객체로 접근하는 것을 통제하기 위해 그 객체의 매니저 또는 자리 채움자를 제공하는 패턴이다.

행위 패턴(Behavioral Patterns)

객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴이다. 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔다.

  1. 역할 사슬 패턴(Chain of Responsibility) : 요청을 처리하는 기회를 하나 이상의 객체에 부여하여 요청을 보내는 쪽과 받는 쪽의 결합을 피하는 패턴이다. 요청을 받는 객체를 연쇄적으로 묶고 객체를 처리할 수 있을 때까지 요청을 전달한다.
  2. 커맨드 패턴(Command) : 요청을 객체로 캡슐화하여 서로 다른 사용자의 매개변수화, 요청 저장 또는 로깅, 연산의 취소를 지원하게 만드는 패턴이다.
  3. 이터레이터 패턴(Iterator) : 내부 표현부를 노출하지 않고 어떤 객체 집합의 원소들을 순차적으로 접근할 수 있는 방법을 제공하는 패턴이다.
  4. 미디에이터 패턴(Mediator) : 한 집합에 속해있는 객체들의 상호 작용을 캡슐화하는 객체를 정의하는 패턴이다. 중재자는 객체들이 직접 서로 참조하지 않도록 함으로써 객체들간의 느슨한 연결을 촉진시키며 객체들의 상호작용을 독립적으로 다양화 시킬 수 있도록 해준다.
  5. 메멘토 패턴(Memento) : 객체의 상태 정보를 저장하고 사용자의 필요에 의하여 원하는 시점의 데이터를 복원할 수 있는 패턴이다. 객체를 이전 상태로 되돌릴 수 있는 기능을 제공한다.
  6. 옵저버 패턴(Observer) : 객체들 사이에 1 : N 의 의존관계를 정의하여 어떤 객체의 상태가 변할 때, 의존관계에 있는 모든 객체들이 통지받고 자동으로 갱신될 수 있게 만드는 패턴이다.
  7. 상태 패턴(State) : 객체의 내부 상태가 변경될 때 동작을 변경하도록 하는 패턴이다. 객체는 자신의 클래스를 변경한 것처럼 보이게 한다.
  8. 스트래티 패턴(Strategy) : 동일 계열의 알고리즘들을 정의하고, 각각 캡슐화하며 이들을 상호교환 가능하도록 한다. 알고리즘을 사용하는 사용자로부터 독립적으로 알고리즘이 변경될 수 있도록 하는 패턴입니다.
  9. 템플릿 메서드 패턴(Template Method) : 슈퍼클래스에서 알고리즘의 골격을 정의하지만, 하위 클래스가 구조를 변경하지 않고 알고리즘의 특정 단계를 재정의할 수 있도록 하는 패턴이다.
  10. 비지터 패턴(Visitor) : 알고리즘이 작동하는 객체와 알고리즘을 구분할 수 있는 패턴이다.

참고


Written by@Myunghwan
Nothing changes if nothing changes

GitHub