디자인 패턴(행동 패턴)

작성일: 2021-12-21 01:17

행동 패턴은 객체나 클래스에 대한 패턴을 정의하는 것이 아닌, 그들 간의 교류 방법에 대해 정의하는 것이다.

이 행동 패턴을 사용하면 우리는 객체 간에 제어 구조보다는 객체들을 어떻게 연결시킬 것인지에 좀더 집중할 수 있다.

# 책임 연쇄(Chain of Responsibility) 패턴

메시지를 보내는 객체와 이를 받아 처리하는 객체들 간의 결합도를 없애기 위한 패턴

  • 하나의 요청에 대한 처리가 반드시 한 객체에서만 되지 않고, 여러 객체에게 그 처리 기회를 주려는 것

이 패턴의 아이디어는 메시지 송신 측과 수신 측을 분리하는 것

  • 송신 측이 자신이 아는 주체에게 처리를 요청하면, 이를 수신한 객체가 자신과 연결된 고리를 따라서 계속 이 요청을 전달하고, 이 중에 한 객체가 적합하다고 판단되면 정의된 서비스를 제공한다.
  • 객체의 연결 고리에 존재하는 객체는 누구든지 동일한 요청을 처리할 수 있어야 하므로 공통된 인터페이스를 가져야 한다.
  • 인터페이스의 기본 구현은 단순히 자신에게 도달한 요청을 연결 고리에 정의된 다음 객체에게 전달하는 것이다. 즉, 내가 처리할 수 있으면 처리하고 아니면 다음 객체에게 넘긴다.

# 활용성

  • 하나 이상의 객체가 요청을 처리해야 하고, 그 요청 처리자 중 어떤것이 선행자인지 모를 때
  • 메시지를 받을 객체를 명시하지 않은 채 여러 객체 중 하나에게 처리를 요청하고 싶을 때
  • 요청을 처리할 수 있는 객체 집합이 동적으로 정의되어야 할 때

# 구조

  • Handler: 요청을 처리하는 인터페이스를 정의하고, 후속 처리자(successor)와 연결을 구현
  • ConcreteHandler: 책임져야 할 행동이 있다면 스스로 요청을 처리하고 그렇지 않으면 후속 처리자에게 요청을 위임

# 결과

  • 객체 간의 행동적 결합도가 적어진다.
    • 다른 객체가 어떻게 요청을 처리하는지 몰라도 되고 단순히 다음 처리자만 인터페이스로 알면되므로 객체 간의 상호작용을 단순화시킨다.
  • 클라이언트 변경없이 새로운 핸들러를 추가할 수 있고 각 핸들러는 자신이 맡은 책임만을 담당할 수 있다.
  • 객체에게 책임을 할당하는 데 유연성을 높일 수 있다.
  • 메시지 수신이 보장되지는 않는다.
    • 메시지를 처리할 객체를 명시적으로 지정하지 않으므로 처리되지 않을 수 있다.

# 사용예

  • 책임 연쇄 패턴은 사용자의 이벤트를 처리하기 위해서 주로 사용된다.
    • 사용자가 특정 위젯의 버튼을 클릭했을 때 버튼 -> 위젯 -> 애플리케이션 순의로 버튼 클릭 이벤트가 연쇄적으로 전달되면서 클릭 이벤트가 처리될 수 있다.

# 명령(Command) 패턴

요청 자체를 캡슐화하여 대기, 로깅, 롤백등의 다양한 연산을 지원할 수 있도록 하는 패턴

# 활용성

  • 수행할 동작을 객체로 매개변수화하고자 할 때(콜백 함수)
  • 서로 다른 시간에 요청을 명시하고. 저장하며, 실행하고 싶을 때
  • 실행 취소 기능을 지원하고 싶을 때

# 구조

  • Command: 연산 수행에 필요한 인터페이스
  • ConcreteCommand: 정해진 명령에 대한 연산을 구현하는 객체로 보통 Receiver를 통해 연산을 수행하지만 Command 자체에서 모든 것을 처리하기도 한다.
    • Receiver 사용 예시로 문서를 붙이는 일을 담당하는 PasteCommandDocument.Paste()를 호출하여 연산을 수행하도록 할 수 있다.
  • Invoker: Command에게 처리를 수행할 것을 요청하는 객체
  • Receiver: 요청에 관련된 연산 수행 방법을 알고 있는 객체

# 결과

  • Command는 연산을 호출하는 개체와 연산 수행 방법을 구현하는 객체를 분리한다.
  • 새로운 Command 객체를 추가하기 쉽다.
  • Command 여러 개를 Composite Command로 만들 수 있다.

# 해석자(Interpreter) 패턴

어떤 언어에 대해, 그 언어의 문법에 대한 표현(expression)을 정의하고 정의된 표현을 통해 해당 언어로 기술된 문장을 해석하는 해석자를 정의하여 반복되는 코드 작성을 줄이고 해당 언어를 사용하여 문제를 해결할 수 있도록 도와주는 패턴

  • 문자열에서 어떤 패턴을 찾는 알고리즘을 매번 작성하기 보다는 정규 표현식을 해석하는 해석자를 한 번 만들어 놓으면 정규 표현식을 통해 문제를 해결할 수 있다.

# 구조

  • Expression: 추상 구문 트리에 속한 노드들의 인터페이스
    • 각 클래스들이 interpret() 연산을 구현하여 새로운 문법이 추가되어도 확장이 가능하다.
  • TerminalExpression: 해석하고자 하는 언어의 문법 규칙에서 단말(terminal) 기호들을 해석하는 표현식
  • NonterminalExpression: 해석하고자 하는 언어의 문법 규칙에서 비단말(nonterminal) 기호들을 해석하는 표현식으로 다른 표현식들을 의존하여 표현식을 제공한다.
  • Context: 문법을 해석하기 위한 컨텍스트

Terminal(단말), NonTerminal(비단말)은 BNF(Backus-Naur Form)에서 사용되는 용어이다.

  • BNF에서 단말 기호는 해당 문법에서 더 이상 전개되지 않는 표현식들을 의미하고, 비단말 기호는 해당 문법에서 연산의 중간 과정이 되는 표현식들을 의미한다.

# 활용성

  • 해석이 필요한 언어가 존재하거나 추상 구문 트리로 그 언어의 문장을 표현하고자 할 때
  • 정의할 언어의 문법이 간단할 때

# 결과

  • 문법의 변경과 확장이 쉽다.
    • 새로운 표현식을 제공하기 위해선 기존 코드의 변경 없이 Expression울 구현하면 된다.
  • 다양한 해석방법을 추가할 수 있다.
    • 표현식에 인터페이스를 추가하여 다양한 방식으로 해석하는 방법들을 제공할 수 있다.
    • 비지터 패턴을 활용하면 인터페이스 변경없이도 가능하다.
  • 복잡한 문법은 관리가 어렵다.
    • 해석자 패턴은 문법의 규칙별로 표현식들을 정의하기 때문에 문법이 복잡해지면 표현식이 매우 많아지기 때문에 관리가 어렵고 문법을 파싱하는 로직이 복잡해진다.

# Interpreter vs Composite

  • Interpreter패턴은 Composite패턴과 구조가 동일하다. 그럼 언제 Interpreter 패턴이라고 할 수 있을까?
    • 한 언어에 대한 일부들을 각 클래스들이 정의하여 해석 방법을 제공해주는 경우 Interpreter 패턴으로 볼 수 있고, 단순히 객체들을 하나의 인터페이스로 다루기 위해 트리 구조를 만든 경우 Composite 패턴이라고 볼 수 있다.

# 사용예

  • 해석자 패턴은 자바에서 컴파일러, 정규 표현식에서 사용된다.

# 중재자(Mediator) 패턴

한 집합에 속해있는 객체의 상호작용을 캡슐화하는 객체를 정의하여 객체 사이의 결합도를 감소시켜 독립적으로 확장 가능하도록 하는 패턴

  • 객체들이 작은 단위로 분할되면 재사용성이 증가하지만 객체간의 상호작용이 급증하여 복잡해질 수 있다. 상호작용과 관련된 행동들을 하나의 중재자 객체로 모아서 처리하면 복잡성을 감소시킬 수 있다.

# 구조

  • Mediator: Colleague 객체와 교류하는 데 필요한 인터페이스를 정의
    • 관련 객체들이 하나의 행동만을 필요로한다면 인터페이스가 없어도 될 것이다.
  • ConcreateMediator: Colleague 객체와 협력하여 행동을 구현하고 Colleague들을 파악하고 관리
  • Colleague 클래스들: 자신의 중재가 객체가 무엇인지 알고 있어 중재자를 통해 중재자 및 관련 객체들과 통신이 가능하다.
    • Colleague 객체들은 서로 직접 통신을 하지 않는다. 중재자와 통신을 위해 주로 감시자(Observer) 패턴을 활용한다.

# 활용성

  • 여러 객체가 잘 정의된 형태이지만 복잡한 상호작용을 가져 시스템을 이해하기 어려울 때
  • 독립된 객체들이 서로 많이 의사소통하여 각 객체들이 재사용하기 어려울 때

# 결과

  • Colleague 객체 사이의 결합도를 줄인다.
  • 객체 프로토콜이 단순화된다.
    • 중재자로 인해 일 대 다 관계도 단순화된다. 중재자가 없다면 다 대 다 관계가 된다.
  • 각 객체의 행동과 상관없이 객체 간의 연결 방법에만 집중할 수 있다.
  • 중재자 객체에서 모든 상호작용을 관리하기 때문에 중재자가 복잡해질 수 있다.

# 사용예

  • UI 시스템에서 행동을 제공하기 위해 여러 위젯들간에 상호작용이 필요할 때 각 위젯들 내부에서 상호작용을 하게되면 위젯들 간의 결합도가 증가하여 확장이 어려워진다.
  • 이 경우 중재자 패턴을 활용하면 각 위젯들의 결합도를 감소시키고 필요한 행동을 중재자 객체에서 제공할 수 있다.

# 관련 패턴

  • 퍼사드 패턴은 객체들로 구성된 서브시스템을 추상화하여 단순화된 인터페이스를 제공하는 것으로 중재자 패턴과는 의도가 다르다.
    • 퍼사드 객체는 서브시스템을 구성하는 객체로 메세지를 전달만 하기 때문에 단방향이다.
    • 중재자 객체는 관련 객체들이 중재자 객체와 통신이 가능하기 때문에 양방향이다.