10-3 설계 패턴(구조 패턴)

윤효준·2025년 7월 27일

소프트웨어 공학

목록 보기
21/43

Adapter (어댑터) 패턴

🎯 의도 (Intent)

  • 서로 호환되지 않는 인터페이스를 가진 클래스들이
    함께 동작할 수 있도록 연결(호환)시켜주는 패턴입니다.
  • 기존 코드를 수정하지 않고도 새로운 코드(클라이언트)가 기존 라이브러리나 클래스와 통합될 수 있게 합니다.
  • 주로 레거시 코드 재사용, 타사 라이브러리 연동 등에 사용됩니다.

👉 즉, 인터페이스가 맞지 않아 함께 사용할 수 없는 두 클래스를 연결
호환성을 제공하는 변환기 역할을 합니다.


🧩 구성 요소 (Participants)

  1. Target (타겟 인터페이스)

    • 클라이언트가 기대하는 인터페이스입니다.
    • 예: request() 메서드를 정의.
  2. Client (클라이언트)

    • Target 인터페이스를 통해 서비스를 사용합니다.
    • Adaptee의 구체적인 구현은 알 필요가 없습니다.
  3. Adaptee (적응 대상 클래스)

    • 기존에 존재하지만, 클라이언트가 기대하는 인터페이스와 호환되지 않는 클래스입니다.
    • 자체적인 메서드(예: specificRequest())를 가집니다.
  4. Adapter (어댑터 클래스)

    • Target 인터페이스를 구현하고,
      내부적으로 Adaptee의 인스턴스를 참조하여 메서드를 변환(호출)합니다.
    • Target → Adapter → Adaptee 형태로 요청이 전달됩니다.

📌 작동 방식

  1. ClientTarget 인터페이스를 통해 요청을 보냅니다.
  2. Adapter는 요청을 받아 Adaptee가 이해할 수 있는 형태로 변환하여 전달합니다.
  3. Adaptee는 실제 작업을 수행하고 결과를 반환합니다.
  4. 클라이언트는 Adaptee의 존재나 내부 구조를 알 필요 없이 서비스를 사용할 수 있습니다.

Bridge (브리지) 패턴

🎯 의도 (Intent)

  • 추상화(Abstraction)와 구현(Implementation)을 분리하여
    서로 독립적으로 확장할 수 있도록 하는 패턴입니다.
  • 클래스 계층이 추상화 부분구현 부분으로 나뉘며,
    이 둘을 브리지(Bridge)로 연결하여 결합도를 낮춥니다.
  • 상속 대신 구성(Composition)을 사용해 유연한 구조를 제공합니다.

👉 즉, 기능 계층(추상화)과 구현 계층을 분리하여
각각 독립적으로 확장 가능하게 만드는 패턴입니다.


🧩 구성 요소 (Participants)

  1. Abstraction (추상화 클래스)

    • 상위 수준의 인터페이스를 정의합니다.
    • 구현 부분(Implementor)에 대한 참조를 포함하고,
      실제 작업을 구현 객체에 위임합니다.
  2. RefinedAbstraction (확장된 추상화 클래스)

    • Abstraction을 확장하여
      추가적인 기능을 정의할 수 있습니다.
  3. Implementor (구현자 인터페이스)

    • 구현 계층의 인터페이스를 정의합니다.
    • 추상화 클래스에서 구현 부분을 호출할 때 사용됩니다.
  4. ConcreteImplementor (구체 구현자)

    • Implementor를 구현하여 구체적인 기능을 제공합니다.
  5. Client (클라이언트)

    • Abstraction을 통해 기능을 사용합니다.
    • 구체 구현에는 직접 접근하지 않으며, 구현 변경에 영향을 받지 않습니다.

📌 작동 방식

  1. ClientAbstraction 객체를 생성하고,
    어떤 Implementor를 사용할지 설정합니다.
  2. Abstraction은 내부적으로 Implementor의 메서드를 호출하여 실제 작업을 수행합니다.
  3. 추상화(기능 계층)구현(플랫폼 계층)을 독립적으로 확장할 수 있어
    새로운 기능이나 구현체 추가 시 기존 코드에 영향을 최소화할 수 있습니다.

Composite (컴포지트) 패턴

🎯 의도 (Intent)

  • 객체들을 트리 구조로 구성하여,
    부분-전체(Part-Whole) 계층 구조를 표현하는 패턴입니다.
  • 개별 객체(Leaf)와 복합 객체(Composite)를 동일하게 다룰 수 있도록 하여,
    클라이언트가 객체의 세부 구조를 신경 쓰지 않고 일관된 방식으로 작업할 수 있게 합니다.

👉 즉, 단일 객체와 복합 객체를 동일한 인터페이스로 처리하여
클라이언트가 트리 구조를 쉽게 다룰 수 있도록 합니다.


🧩 구성 요소 (Participants)

  1. Component (컴포넌트 인터페이스)

    • 트리 구조의 모든 객체(Leaf, Composite)가 공통으로 구현해야 할 인터페이스를 정의합니다.
    • 예: operation() 메서드, 트리 구조 관리 메서드(add(), remove(), getChild()).
  2. Leaf (리프, 잎 노드)

    • 트리의 말단 객체(자식 없음)이며, 실제 동작을 수행합니다.
    • add(), remove() 같은 메서드는 보통 구현되지 않거나 예외를 던집니다.
  3. Composite (복합 객체, 노드)

    • 자식 노드들을 관리하고, Component 인터페이스를 구현합니다.
    • 자신의 자식들에게 작업을 위임하여 전체 트리 구조에서 동작을 수행합니다.
  4. Client (클라이언트)

    • Component 인터페이스만 알고 있으며,
      Leaf와 Composite를 동일한 방식으로 처리할 수 있습니다.

📌 작동 방식

  1. ClientComponent 인터페이스를 사용하여 객체에 접근합니다.
  2. 단일 객체(Leaf)든, 복합 객체(Composite)든 동일한 메서드 호출로 작업이 수행됩니다.
  3. Composite는 요청을 자식들에게 재귀적으로 전달하여 트리 전체에서 작업을 처리합니다.
  4. 새로운 Leaf나 Composite 클래스를 추가하더라도 클라이언트 코드는 변경할 필요가 없습니다.

Decorator (데코레이터) 패턴

🎯 의도 (Intent)

  • 객체에 동적으로 새로운 기능을 추가할 수 있도록 하는 패턴입니다.
  • 상속(Inheritance)을 사용하지 않고, 구성(Composition)을 통해 기능 확장을 제공합니다.
  • 여러 개의 데코레이터를 중첩하면 다양한 기능 조합을 쉽게 만들 수 있습니다.

👉 즉, 기존 객체를 감싸(wrap)서 기능을 확장하고,
클래스 수 증가 없이 유연하게 기능을 추가할 수 있도록 합니다.


🧩 구성 요소 (Participants)

  1. Component (컴포넌트 인터페이스)

    • 기본 객체와 데코레이터가 공통으로 가져야 할 인터페이스를 정의합니다.
    • 예: operation() 메서드.
  2. ConcreteComponent (구체 컴포넌트)

    • 기본 기능을 구현하는 실제 객체입니다.
    • 데코레이터가 감싸는 대상이 됩니다.
  3. Decorator (데코레이터 추상 클래스)

    • Component 인터페이스를 구현하고,
      내부에 Component 참조를 보관합니다.
    • 요청을 위임하면서, 추가 기능을 덧붙일 수 있는 기반을 제공합니다.
  4. ConcreteDecorator (구체 데코레이터)

    • Decorator를 상속하여,
      기존 기능에 새로운 기능을 덧붙이는 구체 클래스입니다.
  5. Client (클라이언트)

    • Component 인터페이스를 사용하여 객체를 다루며,
      객체가 데코레이터로 감싸져 있는지 여부를 신경 쓰지 않습니다.

📌 작동 방식

  1. ClientConcreteComponent를 생성한 후,
    필요에 따라 여러 ConcreteDecorator로 감쌉니다.
  2. 각 데코레이터는 내부적으로 원본 Component의 메서드를 호출하면서
    추가 기능을 수행합니다.
  3. 데코레이터를 중첩하면 기능이 누적적으로 확장됩니다.

Facade (퍼사드) 패턴

🎯 의도 (Intent)

  • 복잡한 서브시스템(Subsystem)의 인터페이스 집합에 대해
    단순하고 통합된 인터페이스(Entry Point)를 제공하는 패턴입니다.
  • 클라이언트가 서브시스템의 내부 세부 사항을 알 필요 없이
    Facade를 통해 간단하게 기능을 사용할 수 있도록 합니다.
  • 의존성을 줄이고(Loosely Coupled), 사용 편의성을 높입니다.

👉 즉, 복잡한 시스템을 단일 진입점으로 감싸서
클라이언트가 쉽게 접근할 수 있도록 단순화합니다.


🧩 구성 요소 (Participants)

  1. Facade (퍼사드 클래스)

    • 클라이언트가 사용하는 간단한 인터페이스를 제공합니다.
    • 내부적으로 여러 서브시스템의 객체를 생성하고,
      적절한 순서로 메서드를 호출하여 요청을 처리합니다.
  2. Subsystem Classes (서브시스템 클래스들)

    • 실제 기능을 수행하는 복잡한 클래스들의 집합입니다.
    • 퍼사드 없이도 직접 사용할 수 있지만,
      Facade를 통해 사용하면 더 단순하고 일관된 접근이 가능합니다.
  3. Client (클라이언트)

    • Facade를 통해 서브시스템을 사용합니다.
    • 서브시스템의 복잡한 세부 사항에는 의존하지 않습니다.

📌 작동 방식

  1. Client는 복잡한 서브시스템 대신 Facade에 요청을 보냅니다.
  2. Facade는 내부적으로 필요한 서브시스템 객체들을 생성하고,
    요청을 처리하기 위해 적절한 메서드를 호출합니다.
  3. 서브시스템이 변경되더라도 Facade만 수정하면 되며,
    클라이언트는 변경의 영향을 받지 않습니다.

Flyweight (플라이웨이트) 패턴

🎯 의도 (Intent)

  • 많은 수의 유사한 객체를 효율적으로 관리하기 위해
    공유 가능한 상태(내부 상태)를 분리하여 메모리 사용을 최소화하는 패턴입니다.
  • 동일한 데이터를 여러 객체가 공유함으로써 객체 생성 비용과 메모리 사용량을 줄입니다.
  • 자주 사용되는 객체를 캐싱(pooling)하여 성능을 최적화할 수 있습니다.

👉 즉, 공유 가능한 상태를 재사용하여
대량의 객체를 효율적으로 관리하는 구조를 제공합니다.


🧩 구성 요소 (Participants)

  1. Flyweight (플라이웨이트 인터페이스)

    • 공유 가능한 객체가 구현해야 할 공통 인터페이스를 정의합니다.
    • 예: operation(extrinsicState) 메서드를 통해 외부 상태를 인자로 전달받아 처리.
  2. ConcreteFlyweight (구체 플라이웨이트)

    • Flyweight 인터페이스를 구현하고,
      내부 상태(Intrinsic State)를 저장합니다.
    • 이 상태는 공유되며 변경되지 않습니다.
  3. UnsharedConcreteFlyweight (비공유 플라이웨이트, 선택적)

    • 공유되지 않는 객체를 정의합니다.
    • 일부 객체는 공유되지 않고 개별적으로 존재할 수 있습니다.
  4. FlyweightFactory (플라이웨이트 팩토리)

    • Flyweight 객체를 캐싱하고 재사용하는 역할을 합니다.
    • 요청받은 객체가 이미 존재하면 기존 인스턴스를 반환하고,
      없으면 새로 생성하여 저장합니다.
  5. Client (클라이언트)

    • FlyweightFactory를 통해 Flyweight 객체를 요청하고 사용합니다.
    • 외부 상태(Extrinsic State)를 유지하고 필요할 때 Flyweight에 전달합니다.

📌 작동 방식

  1. ClientFlyweightFactory를 통해 필요한 객체를 요청합니다.
  2. FlyweightFactory이미 존재하는 객체를 재사용하거나 새로 생성합니다.
  3. ConcreteFlyweight는 공유 가능한 내부 상태를 보관하고,
    외부 상태는 클라이언트가 제공하여 작업을 수행합니다.
  4. 이를 통해 대량의 객체를 효율적으로 관리할 수 있습니다.

Proxy (프록시) 패턴

🎯 의도 (Intent)

  • 다른 객체(RealSubject)에 대한 대리자(Proxy)를 제공하여
    접근 제어, 지연 로딩, 로깅, 캐싱 등의 추가 기능을 수행하는 패턴입니다.
  • 클라이언트는 실제 객체와 동일한 방식으로 프록시를 사용하므로,
    RealSubject의 존재를 몰라도 동작할 수 있습니다.
  • 객체에 대한 접근을 제어하거나 부가적인 작업을 삽입할 때 유용합니다.

👉 즉, 실제 객체에 대한 대리 역할을 수행하는 객체를 사용하여
접근 제어나 부가 기능을 투명하게 추가합니다.


🧩 구성 요소 (Participants)

  1. Subject (주제 인터페이스)

    • RealSubject와 Proxy가 공통으로 구현해야 할 인터페이스를 정의합니다.
    • 클라이언트는 Subject 인터페이스만 알면 됩니다.
  2. RealSubject (실제 객체)

    • Proxy가 대리할 실제 객체입니다.
    • 본래의 핵심 기능을 구현합니다.
  3. Proxy (프록시 클래스)

    • Subject 인터페이스를 구현하고, RealSubject를 참조합니다.
    • 요청을 가로채어 접근 제어, 캐싱, 로깅, 지연 초기화 등의 부가 작업을 수행한 후
      RealSubject에 요청을 위임합니다.
  4. Client (클라이언트)

    • Subject 인터페이스를 사용하여 객체에 접근합니다.
    • 실제 객체인지 프록시인지 구분할 필요가 없습니다.

📌 작동 방식

  1. ClientProxy를 통해 요청을 보냅니다.
  2. Proxy는 필요 시 부가 기능(로그, 권한 체크, 캐싱 등)을 수행하고
    RealSubject에 요청을 위임합니다.
  3. RealSubject는 본래 기능을 수행하고 결과를 반환합니다.
  4. 클라이언트는 ProxyRealSubject의 차이를 인지하지 못합니다.

profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글