10-4 설계 패턴(행위 패턴)

윤효준·2025년 7월 27일

소프트웨어 공학

목록 보기
22/43

Chain of Responsibility (책임 연쇄) 패턴

🎯 의도 (Intent)

  • 요청을 처리할 수 있는 객체를 하나로 고정하지 않고,
    여러 객체가 연쇄적으로 연결되어 순차적으로 처리 기회를 갖도록 하는 패턴입니다.
  • 요청을 보내는 측과 요청을 처리하는 측을 느슨하게 결합(loose coupling)시킵니다.
  • 클라이언트는 누가 요청을 처리할지 알 필요가 없으며,
    체인의 어느 객체가 처리할지 동적으로 결정됩니다.

👉 즉, 요청 처리 책임을 여러 객체에 분산시켜 유연하고 확장 가능한 처리 구조를 만든다.

🧩 구성 요소 (Participants)

  1. Handler (추상 처리자)

    • 요청을 처리할 인터페이스를 정의합니다.
    • 다음 처리자를 가리키는 참조(nextHandler)를 보유하여 체인을 구성합니다.
    • 요청을 처리할 수 없으면 다음 처리자에게 위임합니다.
  2. ConcreteHandler (구체 처리자)

    • Handler를 구현하여 자신이 처리 가능한 요청이면 처리하고,
      그렇지 않으면 다음 처리자에게 요청을 전달합니다.
    • 체인의 일부로 동작하며 각기 다른 처리 로직을 가질 수 있습니다.
  3. Client (클라이언트)

    • 요청을 체인의 첫 번째 처리자에게 전달합니다.
    • 어떤 처리자가 요청을 처리할지 알 필요가 없습니다.

📌 작동 방식

  1. 클라이언트 → 체인의 첫 번째 Handler에게 요청을 보냄

  2. Handler는 요청을 검사

    • 처리 가능 → 요청 처리 후 종료
    • 처리 불가 → 다음 Handler로 전달
  3. 체인의 끝까지 처리자가 없으면 요청은 처리되지 않음


Command (명령) 패턴

🎯 의도 (Intent)

  • 요청(동작)을 객체로 캡슐화하여,
    요청을 매개변수화, 저장, 실행 취소(undo), 재실행(redo)할 수 있도록 하는 패턴입니다.
  • 실행될 작업(메서드 호출)과 요청을 발행하는 객체(클라이언트)를 분리(decouple)합니다.
  • 요청을 실행하는 방법을 변경하지 않고 동작의 큐잉, 로깅, 트랜잭션 처리 등이 가능합니다.

👉 즉, 명령(요청)을 하나의 객체로 만들어서 요청의 발송자와 수신자를 분리하고,
실행 시점이나 실행 방법을 유연하게 관리할 수 있게 해줍니다.

🧩 구성 요소 (Participants)

  1. Command (명령 인터페이스)

    • 실행될 동작을 캡슐화하는 인터페이스를 정의합니다.
    • 보통 execute() 메서드를 선언합니다.
  2. ConcreteCommand (구체 명령)

    • Command 인터페이스를 구현합니다.
    • 실제 요청을 수행할 수신자(Receiver)를 참조하고,
      그 수신자의 메서드를 호출하여 작업을 수행합니다.
  3. Receiver (수신자)

    • 실제 작업을 수행하는 객체입니다.
    • 명령이 전달되면 실질적인 비즈니스 로직을 실행합니다.
  4. Invoker (호출자)

    • Command 객체를 저장하고 필요할 때 execute()를 호출합니다.
    • 여러 명령을 큐에 넣거나, 실행 취소/재실행 로직을 관리할 수 있습니다.
  5. Client (클라이언트)

    • ConcreteCommand 객체를 생성하고 수신자(Receiver)를 연결합니다.
    • Invoker에 명령 객체를 전달하여 실행을 트리거합니다.

📌 작동 방식

  1. Client는 특정 작업을 수행할 ConcreteCommand 객체를 생성하고, Receiver를 설정합니다.
  2. Invoker는 명령 객체를 저장하거나 요청 시 execute()를 호출합니다.
  3. ConcreteCommand는 내부적으로 Receiver의 실제 동작을 호출하여 작업을 수행합니다.

Interpreter (인터프리터) 패턴

🎯 의도 (Intent)

  • 특정 언어나 문법에 대한 해석기를 정의하여,
    문장을 해당 언어의 문법에 따라 해석하고 실행할 수 있도록 하는 패턴입니다.
  • 문법 규칙을 클래스 계층으로 표현하고, 각 규칙을 객체화하여 문장을 해석합니다.
  • 새로운 문법 규칙을 쉽게 추가할 수 있으며, 언어의 문법 구조를 유연하게 확장할 수 있습니다.

👉 즉, 언어의 문법을 클래스로 표현하고, 해당 문법을 해석할 수 있는 구조를 제공하여
간단한 언어나 명령어 집합을 해석할 때 유용합니다.

🧩 구성 요소 (Participants)

  1. AbstractExpression (추상 표현식)

    • 해석(interpret) 작업을 위한 인터페이스를 정의합니다.
    • 모든 구체 표현식(terminal/non-terminal)이 이를 구현합니다.
  2. TerminalExpression (종단 표현식)

    • 문법의 기본 단위(터미널 심볼)를 해석하는 클래스입니다.
    • 예: 숫자, 변수, 리터럴 등.
  3. NonTerminalExpression (비종단 표현식)

    • 다른 표현식들을 조합하여 더 복잡한 문법 규칙을 정의합니다.
    • 예: 덧셈, 뺄셈, 조건식 등 연산자를 해석하는 클래스.
  4. Context (문맥)

    • 해석 과정에서 필요한 전역 정보(환경, 변수 값, 심볼 테이블 등)를 저장하고 제공합니다.
  5. Client (클라이언트)

    • 문법 규칙에 맞는 문장을 구문 트리(abstract syntax tree, AST) 형태로 작성하고,
      해당 트리의 interpret() 메서드를 호출하여 해석을 수행합니다.

📌 작동 방식

  1. Client는 해석할 문장을 구문 트리로 구성합니다.
  2. 구문 트리의 각 노드는 AbstractExpression을 구현한 객체입니다.
  3. 클라이언트가 루트 표현식의 interpret(context)를 호출하면,
    트리의 각 노드가 재귀적으로 해석 작업을 수행합니다.

Iterator (반복자) 패턴

🎯 의도 (Intent)

  • 집합 객체(Aggregate)의 내부 구조를 노출하지 않고,
    그 요소들에 순차적으로 접근할 수 있는 방법을 제공합니다.
  • 컬렉션의 순회 로직을 별도의 객체(Iterator)로 분리하여,
    여러 방식의 순회를 쉽게 구현할 수 있습니다.

👉 즉, 컬렉션 내부 구조를 숨기면서도 통일된 인터페이스로 요소를 순차적으로 접근하도록 합니다.

🧩 구성 요소 (Participants)

  1. Iterator (반복자 인터페이스)

    • 요소를 순차적으로 접근하는 인터페이스를 정의합니다.
    • 보통 hasNext(), next() 같은 메서드를 포함합니다.
  2. ConcreteIterator (구체 반복자)

    • Iterator 인터페이스를 구현하여 집합 객체의 현재 위치를 추적하고,
      실제 순회 로직을 수행합니다.
  3. Aggregate (집합체 인터페이스)

    • 반복자를 생성하는 인터페이스를 정의합니다.
    • 보통 createIterator() 메서드를 가집니다.
  4. ConcreteAggregate (구체 집합체)

    • Aggregate 인터페이스를 구현합니다.
    • 자신이 관리하는 컬렉션에 대해 ConcreteIterator를 생성하여 반환합니다.
  5. Client (클라이언트)

    • Aggregate로부터 Iterator를 받아 요소를 순차적으로 접근합니다.
    • 컬렉션의 내부 구현을 알 필요 없이 Iterator의 메서드만 사용합니다.

📌 작동 방식

  1. ClientConcreteAggregate에서 createIterator()를 호출해 Iterator를 얻습니다.
  2. Iterator는 내부적으로 현재 위치를 추적하며, hasNext()next()를 통해 요소를 하나씩 반환합니다.
  3. 클라이언트는 컬렉션 구조를 몰라도 안전하게 순회할 수 있습니다.

Mediator (중재자) 패턴

🎯 의도 (Intent)

  • 객체 간의 복잡한 상호작용을 캡슐화하여,
    객체들이 서로를 직접 참조하지 않고 중재자(Mediator)를 통해서만 소통하도록 만드는 패턴입니다.
  • 객체 간의 의존성을 줄이고(느슨한 결합), 상호작용 로직을 한 곳에 모아 유지보수성을 높입니다.

👉 즉, 여러 객체 간의 복잡한 관계를 하나의 중재자 객체로 캡슐화하여
객체들이 서로 독립적으로 존재하면서도 협력할 수 있게 합니다.

🧩 구성 요소 (Participants)

  1. Mediator (중재자 인터페이스)

    • 동료(Colleague) 객체 간의 상호작용을 캡슐화하는 인터페이스를 정의합니다.
    • 보통 notify(sender, event)와 같은 메서드를 가집니다.
  2. ConcreteMediator (구체 중재자)

    • Mediator 인터페이스를 구현합니다.
    • 여러 Colleague 간의 의사소통 로직을 중앙에서 관리하며,
      특정 이벤트가 발생하면 적절한 Colleague에 메시지를 전달합니다.
  3. Colleague (동료 클래스, 컴포넌트)

    • Mediator를 참조하여 다른 객체와 통신합니다.
    • 서로 직접 참조하지 않고, 모든 요청을 Mediator를 통해 전달합니다.
  4. ConcreteColleague (구체 동료 클래스)

    • Colleague를 구현하며,
      자신의 상태 변경이나 이벤트를 Mediator에 알리고,
      Mediator로부터 명령이나 메시지를 받아 동작합니다.
  5. Client (클라이언트)

    • ConcreteMediator를 생성하고, Colleague 객체들을 Mediator와 연결합니다.
    • 실제 상호작용은 Mediator를 통해 이루어집니다.

📌 작동 방식

  1. Client는 여러 ConcreteColleagueConcreteMediator를 생성하고 연결합니다.
  2. ConcreteColleague가 이벤트를 발생시키면, 이를 Mediator에 알립니다.
  3. Mediator는 다른 적절한 Colleague에게 메시지를 전달하거나 명령을 내립니다.
  4. Colleague들은 서로 직접 참조하지 않고 Mediator만 통해 상호작용합니다.

Memento (메멘토) 패턴

🎯 의도 (Intent)

  • 객체의 이전 상태를 저장하고 필요할 때 복원(restore)할 수 있도록 하는 패턴입니다.
  • 객체의 내부 상태를 캡슐화하여 저장하면서도,
    캡슐화를 위반하지 않고 외부에서 상태를 직접 접근하지 못하도록 합니다.
  • 주로 실행 취소(Undo), 되돌리기(Redo), 버전 관리 기능을 구현할 때 사용됩니다.

👉 즉, 객체의 상태를 안전하게 저장해 두었다가
필요할 때 이전 상태로 복원할 수 있는 메커니즘을 제공합니다.

🧩 구성 요소 (Participants)

  1. Memento (메멘토)

    • Originator(원본 객체)의 내부 상태를 저장하는 객체입니다.
    • 외부에는 상태 세부사항을 노출하지 않으며,
      Originator만이 이 객체의 상태를 읽거나 복원할 수 있습니다.
  2. Originator (원본 객체)

    • 자신의 현재 상태를 Memento 객체에 저장하고,
      필요할 때 Memento를 통해 상태를 복원합니다.
    • createMemento()restoreMemento(memento) 같은 메서드를 가집니다.
  3. Caretaker (관리자)

    • Memento 객체를 보관하지만, 그 내부 상태에는 접근할 수 없습니다.
    • 여러 개의 Memento를 저장하여 Undo/Redo 스택을 구현할 수도 있습니다.
  4. Client (클라이언트)

    • Originator와 Caretaker를 사용하여 상태 저장 및 복원을 수행합니다.

📌 작동 방식

  1. Originator가 현재 상태를 캡슐화한 Memento를 생성합니다.
  2. Caretaker는 이 Memento를 저장하지만 상태 세부 정보는 알 수 없습니다.
  3. 복원이 필요할 때, Caretaker가 저장된 Memento를 Originator에 전달합니다.
  4. Originator는 해당 Memento의 정보를 사용하여 상태를 이전 시점으로 복원합니다.

Observer (옵서버) 패턴

🎯 의도 (Intent)

  • 한 객체(Subject)의 상태 변화를 여러 다른 객체(Observers)에게
    자동으로 알리고, 동기화할 수 있도록 하는 패턴입니다.
  • 객체 간의 일대다(1\:N) 관계를 정의하여,
    Subject가 변경되면 모든 Observer가 자동으로 갱신됩니다.
  • Subject와 Observer 간의 결합도를 낮추고, 확장성을 높입니다.

👉 즉, 어떤 객체의 상태 변화가 있을 때,
연결된 다른 객체들이 자동으로 통보받아 반응할 수 있도록 합니다.

🧩 구성 요소 (Participants)

  1. Subject (주제, 발행자)

    • 옵서버를 등록(attach)하거나 제거(detach)하는 메서드를 정의합니다.
    • 상태가 변경되면 모든 옵서버에게 알림(notify)을 전송합니다.
  2. ConcreteSubject (구체 주제)

    • Subject를 구현하고, 실제 상태를 보유합니다.
    • 상태 변경 시 notify()를 호출하여 옵서버들에게 알립니다.
  3. Observer (옵서버 인터페이스)

    • Subject로부터 알림을 받기 위한 update() 메서드를 정의합니다.
  4. ConcreteObserver (구체 옵서버)

    • Observer 인터페이스를 구현합니다.
    • Subject의 상태 변경 알림을 받아 자신의 상태를 갱신합니다.
  5. Client (클라이언트)

    • Subject와 Observer들을 생성하고 관계를 설정합니다.

📌 작동 방식

  1. ClientConcreteSubject와 하나 이상의 ConcreteObserver를 생성합니다.
  2. 옵서버들은 Subject에 자신을 등록(attach)합니다.
  3. ConcreteSubject의 상태가 변경되면 notify()를 통해 모든 옵서버에 알립니다.
  4. ConcreteObserverupdate()를 호출받아 자동으로 상태를 갱신합니다.

State (상태) 패턴

🎯 의도 (Intent)

  • 객체의 내부 상태에 따라 행동(behavior)이 달라지도록 하는 패턴입니다.
  • 상태별 로직을 별도의 상태 클래스로 캡슐화하여,
    객체가 상태 전환 시 클래스를 교체하는 방식으로 행동을 변경합니다.
  • if-elseswitch문으로 상태를 분기하는 대신,
    상태 객체에 위임하여 유연하고 확장 가능한 상태 전환 로직을 제공합니다.

👉 즉, 객체의 상태 변화를 객체화하여
상태에 따른 행동 변화를 자연스럽게 처리할 수 있게 합니다.

🧩 구성 요소 (Participants)

  1. Context (문맥 클래스)

    • 현재 상태 객체(State)를 유지하고,
      상태 관련 요청을 현재 상태 객체에 위임합니다.
    • 상태 전환 시 내부적으로 State 객체를 교체합니다.
  2. State (상태 인터페이스)

    • Context에서 호출할 상태별 행동 메서드를 정의합니다.
    • 예: handle(), request() 등의 메서드.
  3. ConcreteState (구체 상태 클래스)

    • State 인터페이스를 구현하여,
      특정 상태에서의 행동 로직을 정의합니다.
    • 필요 시 Context의 상태를 다른 상태 객체로 변경할 수 있습니다.
  4. Client (클라이언트)

    • Context 객체를 사용하며, 상태 전환은 Context 내부에서 처리되므로
      클라이언트는 상태 변경 로직을 알 필요가 없습니다.

📌 작동 방식

  1. Client는 Context 객체를 생성하고 초기 상태를 설정합니다.
  2. Context가 요청을 받으면 현재 State 객체의 메서드에 위임합니다.
  3. ConcreteState는 행동을 수행하고, 필요 시 Context의 상태를 다른 State로 변경합니다.
  4. 상태 전환이 일어나도 클라이언트 코드에는 변경이 필요 없습니다.

Strategy (전략) 패턴

🎯 의도 (Intent)

  • 알고리즘(행동) 군(群)을 정의하고, 각각을 캡슐화하여
    상호 교환 가능하게 만드는 패턴입니다.
  • 클라이언트는 런타임에 알고리즘을 선택하거나 교체할 수 있으며,
    알고리즘의 변경이 클라이언트 코드에 영향을 주지 않습니다.
  • if-elseswitch문으로 여러 알고리즘을 분기하는 대신,
    전략 객체(Strategy)를 위임하여 유연한 설계를 제공합니다.

👉 즉, 동일한 문제를 해결하는 여러 알고리즘을 정의하고,
필요에 따라 쉽게 교체할 수 있도록 만드는 패턴입니다.

🧩 구성 요소 (Participants)

  1. Strategy (전략 인터페이스)

    • 알고리즘(행동)을 수행하는 공통 인터페이스를 정의합니다.
    • 예: execute(), sort(), calculate() 등의 메서드.
  2. ConcreteStrategy (구체 전략 클래스)

    • Strategy 인터페이스를 구현하여 특정 알고리즘 로직을 제공합니다.
    • 서로 다른 알고리즘이 여러 개 존재할 수 있으며, 필요에 따라 교체됩니다.
  3. Context (문맥 클래스)

    • Strategy 객체를 참조하고,
      특정 작업을 수행할 때 현재 설정된 Strategy 객체에 알고리즘 실행을 위임합니다.
    • 전략을 런타임에 교체할 수 있습니다.
  4. Client (클라이언트)

    • 사용할 ConcreteStrategy를 선택하여 Context에 전달합니다.
    • 알고리즘의 구체적인 구현은 몰라도 됩니다.

📌 작동 방식

  1. Client가 사용할 알고리즘(ConcreteStrategy)을 선택하고, Context에 주입합니다.
  2. Context는 요청을 받을 때 알고리즘 실행을 현재 Strategy 객체에 위임합니다.
  3. 전략을 교체하면 Context의 동작 방식도 자연스럽게 변경됩니다.

Template Method (템플릿 메서드) 패턴

🎯 의도 (Intent)

  • 알고리즘의 골격(템플릿)을 정의하고,
    알고리즘의 일부 단계를 서브클래스에서 재정의(override)할 수 있도록 하는 패턴입니다.
  • 알고리즘의 구조는 상위 클래스에 두고, 세부 단계는 하위 클래스에서 구현하여
    코드 재사용성을 높이고 알고리즘 변형을 쉽게 만듭니다.
  • 상위 클래스는 알고리즘의 전체 흐름을 제어하면서,
    하위 클래스에 구체적 세부 동작만 위임합니다.

👉 즉, 공통 로직은 상위 클래스에, 변화되는 부분은 하위 클래스에서 구현하도록 하여
알고리즘의 일관성을 유지하면서 확장을 용이하게 합니다.

🧩 구성 요소 (Participants)

  1. AbstractClass (추상 클래스)

    • 템플릿 메서드(Template Method)를 정의하며,
      알고리즘의 전체 흐름(단계)을 구현합니다.
    • 일부 단계는 추상 메서드(abstract method) 또는 hook 메서드로 선언하여
      서브클래스가 구체적으로 구현하도록 합니다.
  2. ConcreteClass (구체 클래스)

    • AbstractClass에서 정의한 추상 메서드들을 구현하여
      알고리즘의 특정 단계를 구체적으로 수행합니다.
  3. Client (클라이언트)

    • AbstractClass의 인터페이스를 사용하여 알고리즘을 실행합니다.
    • 알고리즘의 흐름은 동일하지만, 구체적 동작은 하위 클래스에 의해 달라집니다.

📌 작동 방식

  1. ClientAbstractClass를 참조하여 템플릿 메서드를 호출합니다.
  2. 템플릿 메서드는 알고리즘의 전체적인 절차를 고정해 놓고,
    일부 단계의 세부 구현은 ConcreteClass에서 제공됩니다.
  3. 하위 클래스가 바뀌더라도 템플릿의 전체 구조는 변경되지 않습니다.

Visitor (방문자) 패턴

🎯 의도 (Intent)

  • 객체 구조(요소들)를 변경하지 않고,
    객체들에 대해 수행할 새로운 연산(기능)을 추가할 수 있도록 하는 패턴입니다.
  • 데이터 구조(요소 클래스)와 연산(Visitor)을 분리하여,
    기능 확장이 용이하고, 요소 클래스의 수정 없이 새로운 연산을 추가할 수 있습니다.
  • 기존 클래스에 연산을 추가하기 위해 클래스를 수정하는 문제(개방-폐쇄 원칙 위배)를 방지합니다.

👉 즉, 요소 클래스는 그대로 두고,
방문자 객체(Visitor)를 통해 새로운 동작을 외부에서 주입할 수 있도록 합니다.

🧩 구성 요소 (Participants)

  1. Visitor (방문자 인터페이스)

    • 각 요소(Element) 타입에 대해 방문할 메서드를 정의합니다.
    • 예: visitConcreteElementA(), visitConcreteElementB().
  2. ConcreteVisitor (구체 방문자)

    • Visitor 인터페이스를 구현하여,
      각 요소별로 수행할 실제 연산을 정의합니다.
  3. Element (요소 인터페이스)

    • 방문자를 받아들이기 위한 accept(visitor) 메서드를 정의합니다.
    • 이 메서드는 방문자를 호출하여 자신의 타입에 맞는 visit 메서드를 실행하게 합니다.
  4. ConcreteElement (구체 요소 클래스)

    • Element를 구현하고, accept(visitor)에서 visitor.visitConcreteElement(this)를 호출합니다.
    • 방문자에게 자신을 전달하여, 방문자가 해당 요소에 맞는 연산을 수행하도록 합니다.
  5. ObjectStructure (객체 구조)

    • 여러 요소 객체들을 포함하거나 관리합니다.
    • 방문자를 받아 모든 요소에 대해 순회하며 accept(visitor)를 호출합니다.
  6. Client (클라이언트)

    • VisitorObjectStructure를 생성하고,
      방문자를 요소 집합에 적용하여 원하는 연산을 수행합니다.

📌 작동 방식

  1. ClientConcreteVisitor를 생성하고 ObjectStructure에 전달합니다.
  2. ObjectStructure는 내부 요소들을 순회하며 각 요소의 accept(visitor)를 호출합니다.
  3. 요소는 자신을 visitor에게 넘겨주며, visitor는 해당 요소 타입에 맞는 연산을 실행합니다.
  4. 새로운 연산을 추가할 때는 새로운 Visitor만 추가하면 되며, 기존 요소 클래스는 수정할 필요가 없습니다.
profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글