Design Pattern

[디자인 패턴] 중재자 패턴 (Mediator Pattern)

꾸준함. 2024. 6. 30. 01:12

중재자 패턴

  • 객체 지향 프로그래밍에서 객체 간의 상호작용을 간소화하고 캡슐화하는 데 사용되는 패턴
  • 객체들이 서로 직접 통신하는 것을 피하고 중앙에 있는 중재자 객체를 통해 통신하게 함으로써 여러 컴포넌트 간의 결합도를 중재자를 통해 낮출 수 있음

 

https://medium.com/cp-massive-programming/mediator-cheat-sheet-840f7db56437

 

주요 구성 요소

 

1. Mediator

  • 중재자 역할을 수행하는 인터페이스를 정의

 

2. ConcreteMediator

  • Mediator 인터페이스 구현체
  • 객체들 간의 상호작용을 조정

 

3. Colleague

  • Mediator와 상호작용하는 객체
  • 일반적으로 Colleague 객체들은 Mediator 객체를 통해서만 서로 통신

 

4. ConcreteColleague

  • Colleague 인터페이스 구현체
  • 해당 객체들은 Mediator를 통해 다른 Colleague들과 통신

 

중재자 패턴 구현 예시

 

1. ChatMediator (Mediator)

 

interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
view raw .java hosted with ❤ by GitHub

 

 

2. ChatMediatorImpl (ConcreteMediator)

 

class ChatMediatorImpl implements ChatMediator {
private List<User> users;
public ChatMediatorImpl() {
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
this.users.add(user);
}
@Override
public void sendMessage(String message, User user) {
for (User u : this.users) {
// 메시지를 보낸 사용자를 제외한 모든 사용자에게 메시지를 전달
if (u != user) {
u.receive(message);
}
}
}
}
view raw .java hosted with ❤ by GitHub

 

 

3. User (Colleague)

 

abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public abstract void send(String message);
public abstract void receive(String message);
}
view raw .java hosted with ❤ by GitHub

 

 

4. UserImpl (ConcreteColleague)

 

class UserImpl extends User {
public UserImpl(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
System.out.println(this.name + "가 보내는 메시지: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + "가 받은 메시지: " + message);
}
}
view raw .java hosted with ❤ by GitHub

 

 

5. 클라이언트 코드

 

public class Client {
public static void main(String[] args) {
ChatMediator mediator = new ChatMediatorImpl();
User chulsoo = new UserImpl(mediator, "철수");
User hooni = new UserImpl(mediator, "훈이");
User zzanggu = new UserImpl(mediator, "짱구");
User maenggu = new UserImpl(mediator, "맹구");
User yuri = new UserImpl(mediator, "유리");
mediator.addUser(chulsoo);
mediator.addUser(hooni);
mediator.addUser(zzanggu);
mediator.addUser(maenggu);
mediator.addUser(yuri);
chulsoo.send("안녕하세요, 여러분!");
}
}
view raw .java hosted with ❤ by GitHub

 

부연 설명

  • ChatMediator가 중재자 역할을 하고, User 클래스가 Colleague 역할
  • User 객체들은 ChatMediator를 통해서만 서로 통신하여 객체 간의 결합도를 줄이고 유지보수가 용이해짐

 

중재자 패턴 장단점

 

장점

  • 객체들이 직접 서로 통신하지 않고 중재자를 통해 통신하므로, 객체 간의 결합도가 낮아짐
  • 객체 간의 복잡한 상호작용을 중앙에서 관리하므로, 각 객체는 자신의 역할에만 집중할 수 있음 (SOLID의 SRP 원칙)
  • 새로운 객체를 추가할 때 기존 객체를 변경할 필요가 없으며 중재자를 통해 새로운 객체를 추가하면 되므로 시스템의 확장성이 높아짐 (SOLID의 OCP 원칙)

 

단점

  • 중재자 역할을 하는 클래스의 복잡도와 결합도가 높아짐
    • 중재자가 시스템의 또 다른 단일 실패 지점(single point of failure)이 될 수 있음

 

  • 객체들이 직접 통신하지 않고 중재자를 통해 통신하기 때문에 디버깅이 어려울 수 있음
    • 중재자를 통해 발생하는 모든 상호작용을 추적해야 함

 

실무에서 쓰이는 중재자 패턴

 

1. 자바의 ExecutorService

  • 자바의 java.util.concurrent 패키지에서 제공하는 인터페이스로, 비동기 작업을 관리하고 실행하는 메커니즘을 제공
  • ExecutorService는 스레드 풀을 사용하여 작업을 처리하며, 이를 통해 애플리케이션의 쓰레드 관리를 단순화하고 효율성을 높임
  • 중재자 패턴의 관점에서 살펴보면, ExecutorService가 중재자 역할을 하고, 작업(Task)들이 Colleague 역할을 수행한다고 볼 수 있음
    • ExecutorService는 중재자로서 역할을 수행하며, 작업(Task)들의 실행을 관리
    • Task들이 서로 직접 쓰레드 관리를 하지 않고, ExecutorService를 통해 간접적으로 스레드를 사용
    • Runnable 또는 Callable 인터페이스를 구현하는 작업들이 Colleague 역할

 

2. 스프링 DispatcherServlet

  • Spring Framework의 DispatcherServlet은 웹 애플리케이션에서 클라이언트 요청을 처리하고 적절한 컨트롤러로 요청을 라우팅 하는 역할
  • 중재자 패턴의 관점에서 볼 때, DispatcherServlet은 중재자 역할을 수행하며, 여러 컨트롤러들이 Colleague 역할

 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
// 핸들러 매핑
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 핸들러 어댑터 가져오기
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 실제 핸들러 호출
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 뷰 이름 해석
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
} else {
response.flushBuffer();
}
} catch (Exception ex) {
dispatchException = ex;
} catch (Error err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
} finally {
if (dispatchException != null) {
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} else {
processDispatchResult(processedRequest, response, mappedHandler, mv, null);
}
}
}
view raw .java hosted with ❤ by GitHub

 

 

부연 설명

  • DispatcherServlet은 중재자로서 클라이언트 요청을 받아 여러 구성 요소들 간의 상호작용을 조정하며 doDispatch 메서드는 이러한 역할을 잘 보여줌
  • 다양한 컴포넌트가 Colleague 역할
    • HandlerMapping: 요청 URL을 기반으로 적절한 핸들러를 찾는 역할을 수행하며 여러 종류의 HandlerMapping이 존재할 수 있고 각각의 역할은 서로 독립적
    • HandlerAdapter: 찾은 핸들러를 실행할 수 있는 어댑터를 제공하며 핸들러의 타입에 따라 적절한 어댑터가 선택됨
    • ViewResolver: 핸들러가 반환한 뷰 이름을 실제 View 객체로 변환하며 여러 ViewResolver가 존재할 수 있고 각각의 역할은 서로 독립적

 

참고

코딩으로 학습하는 GoF의 디자인 패턴 - 백기선 강사님

반응형