본문 바로가기
Design Pattern/행동 패턴(Behavioral patterns)

Mediator Pattern, 중재자 패턴

by codeyaki 2024. 3. 7.
반응형

중재자 패턴이란?

객체들 간의 상호작용을 중재하는 패턴이다. 

객체들끼리 직접 통신하는 방식이 아닌 중재자를 통해서 상호작용하도록 하는 방식이다.

항공기 관제탑을 보면 이해할 수 있다. 비행기끼리 통신을 할 수는 있지만 모든 통신을 각자 하게 되면 통신을 받지 못하는 비행기도 생기고, 통신이 매우 난잡해질 것이다. 그래서 모든 비행기들은 관제탑을 통해서 통신을 하면서 조율을 하게 된다.

인천 관제탑 by인천국제항공사

중재자 패턴을 왜 사용해야 하는가?

  • 객체 간의 결합도를 낮춰준다.
  • 새로운 객체들을 도입할 수 있다.
  • 단일책임원칙을 지켜 유지보수와 재사용을 쉽도록 만든다.

패턴을 언제 사용해야 하는가?

  • 객체들이 서로 복잡하고 강하게 결합되어 있는 경우
  • 객체들간 직접적인 상호작용이 코드를 복잡하게 만들어 유지보수가 어려운 경우 

문제점

  • 중재자가 슈퍼맨이 될 수 있다. 즉, 중재자가 너무 많은 책임을 갖게 될 수 있다는 것이다.
  • 중재자를 통해서 간접적으로 상호작용하기 때문에 성능이 비교적 느려질 수 있다.

구현 예시(자바)

패턴을 적용하지 않았을 때

먼저 중재자 패턴 없이 각 객체 간에 통신을 직접 만들 때를 예로 들었다.

간단한 채팅을 구현해보는 것을 예시로 한다. (실제 각자 화면이 아닌 콘솔상에서...)

먼저 채팅을 할 유저를 구현해 준다.

package com.example.designpattern.behavioral.mediator.nopattern;

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public void sendMessage(String msg, User toUser){
        toUser.receiveMessage(msg, this);
    }

    public void receiveMessage(String msg, User fromUser){
        System.out.println(fromUser.name + " >> " + this.name + ": " + msg);
    }
}

간단하게 이름을 가지고 있고 메시지 전송과 수신 두 가지 동작을 할 수 있는 객체이다.

이제 이 유저들이 서로 채팅을 한다고 가정하고 메인 메서드를 구현해 보자.

package com.example.designpattern.behavioral.mediator.nopattern;

import java.util.ArrayList;
import java.util.List;

public class NoMediatorClient {
    public static void main(String[] args) {

        // 방 생성
        List<User> userList = new ArrayList<>();

        // 유저 입장
        User user1 = new User("user1");
        User user2 = new User("user2");
        User user3 = new User("user3");
        User user4 = new User("user4");

        userList.addAll(List.of(user1, user2, user3, user4));

        sendToAll(user1, "안녕 애들아", userList);

        // 귓속말
        user1.sendMessage("user2야 나랑 놀자", user2);
        user2.sendMessage("그래 user1아", user1);
    }

    private static void sendToAll(User from, String msg, List<User> userList){
        for (User user : userList) {
            if (from != user) {
                from.sendMessage(msg, user);
            }
        }
    }
}

현재 User 클래스만을 사용했는데도 복잡해지는 모습을 볼 수 있다. 여기서 만약 익명 유저와 어드민이 추가된 상태에서 구현한다면...? 매우 복잡해져서 감당하기가 힘들어질 것이다..

 

중재자 패턴 적용기

이번엔 중재자 패턴을 적용시켜서 구현해 보자

일반 유저, 익명 유저, 관리자 유저를 만들어보자

세 유저를 추상화여 User 클래스를 먼저 만들어준다.

package com.example.designpattern.behavioral.mediator.pattern.user;

import com.example.designpattern.behavioral.mediator.pattern.mediator.Mediator;
import lombok.Getter;

public class User {
    @Getter
    private String name;

    protected Mediator mediator;

    public User(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public void sendMessageAll(String msg){
        mediator.sendMessageAll(this, msg);
    }

    public void sendMessage(User to, String msg){
        mediator.sendMessage(to, this, msg);
    }
    public void receiveMessage(User from, String msg){
        System.out.println(from.getName() + " >> " + this.name + ": " + msg);
    }
}

 

그다음 각각의 유저들을 구현해 준다.

이때 익명의 유저는 1:1 대화 전송을 불가능하도록 하고 관리자계정은 관리자가 보내는 메시지임을 알 수 있도록 효과를 넣어준다.

package com.example.designpattern.behavioral.mediator.pattern.user;

import com.example.designpattern.behavioral.mediator.pattern.mediator.Mediator;

public class NormalUser extends User {

    public NormalUser(String name, Mediator mediator) {
        super(name, mediator);
    }
}

package com.example.designpattern.behavioral.mediator.pattern.user;

import com.example.designpattern.behavioral.mediator.pattern.mediator.Mediator;

public class AnonymousUser extends User{
    public AnonymousUser(String name, Mediator mediator) {
        super(name, mediator);
    }

    @Override
    public void sendMessage(User to, String msg) {
        System.out.println("익명 유저는 메시지를 전송할 수 없습니다.");
    }
}

package com.example.designpattern.behavioral.mediator.pattern.user;

import com.example.designpattern.behavioral.mediator.pattern.mediator.Mediator;

public class AdminUser extends User{
    public AdminUser(String name, Mediator mediator) {
        super(name, mediator);
    }

    @Override
    public void sendMessageAll( String msg) {
        super.sendMessageAll("[ADMIN] **" + msg + "**");
    }

    @Override
    public void sendMessage(User to, String msg) {
        super.sendMessage(to, "[ADMIN] **" + msg + "**");
    }

    public void kickUser(User user, String cause) {
        this.sendMessageAll(user.getName() + "를 " + cause + "의 원인으로 추방합니다.");
        mediator.removeUser(user);
    }
}

 

 

이제 각 유저들 간의 통신을 담당할 중재자를 구현하여 준다.

package com.example.designpattern.behavioral.mediator.pattern.mediator;

import com.example.designpattern.behavioral.mediator.pattern.user.User;

public interface Mediator {
    void sendMessageAll(User from, String msg);
    void sendMessage(User from, User to, String msg);
    void addUser(User user);
    void removeUser(User user);
}

 

package com.example.designpattern.behavioral.mediator.pattern.mediator;

import com.example.designpattern.behavioral.mediator.pattern.user.User;

import java.util.ArrayList;
import java.util.List;

public class ChatMediator implements Mediator {
    private final List<User> users;

    public ChatMediator() {
        this.users = new ArrayList<>();
    }

    @Override
    public void sendMessageAll(User from, String msg) {
        for (User user : users) {
            if(user == from) continue;
            user.receiveMessage(from, msg);
        }
    }

    @Override
    public void sendMessage(User from, User to, String msg) {
        to.receiveMessage(from, msg);
    }

    @Override
    public void addUser(User user) {
        sendMessageAll(user, user.getName() + "님이 접속하였습니다.");
        this.users.add(user);

    }

    @Override
    public void removeUser(User user) {
        sendMessageAll(user, user.getName() + "님이 퇴장하였습니다.");
        this.users.add(user);
    }
}

 

 

 

중재자를 이용해서 각 객체 간에 상호작용을 구현해 보자.

package com.example.designpattern.behavioral.mediator.pattern;

import com.example.designpattern.behavioral.mediator.pattern.mediator.ChatMediator;
import com.example.designpattern.behavioral.mediator.pattern.mediator.Mediator;
import com.example.designpattern.behavioral.mediator.pattern.user.AdminUser;
import com.example.designpattern.behavioral.mediator.pattern.user.NormalUser;
import com.example.designpattern.behavioral.mediator.pattern.user.AnonymousUser;
import com.example.designpattern.behavioral.mediator.pattern.user.User;

public class MediatorClient {
    public static void main(String[] args) {
        Mediator mediator = new ChatMediator();
        User anonymousUser = new AnonymousUser("익명유저", mediator);
        AdminUser admin = new AdminUser("어드민", mediator);
        User user1 = new NormalUser("노말유저1", mediator);
        User user2 = new NormalUser("노말유저2", mediator);
        User user3 = new NormalUser("노말유저3", mediator);

        System.out.println("========= 익명유저 입장 =========");
        mediator.addUser(anonymousUser);
        System.out.println("========= 어드민 입장 =========");
        mediator.addUser(admin);
        System.out.println("========= 유저들 입장 =========");
        mediator.addUser(user1);
        mediator.addUser(user2);
        mediator.addUser(user3);

        System.out.println("========= 유저1 메시지 =========");
        user1.sendMessageAll("안녕하세요!");

        System.out.println("========= 유저1 <-> 유저2 =========");
        user1.sendMessage(user2, "안녕 user2야");
        user2.sendMessage(user1, "반가워 suer1아");

        System.out.println("========= 어드민 공지 =========");
        admin.sendMessageAll("반가워요");

        System.out.println("========= 추방하기 =========");
        admin.kickUser(user2, "도배");

        System.out.println("========= 익명유저 -> 유저1 =========");
        anonymousUser.sendMessage(user1, "난 익명이지롱 바부야");

    }
}

 

이전 패턴을 적용하기 전에는 직접 메서드를 만들어 구현을 했어야 했지만 이제는 중재를 통해서 통신을 하기 때문에 손쉽게 객체를 추가하고 변경할 수 있게 되었다.

실행결과는 다음과 같다

========= 익명유저 입장 =========
========= 어드민 입장 =========
어드민 >> 익명유저: 어드민님이 접속하였습니다.
========= 유저들 입장 =========
노말유저1 >> 익명유저: 노말유저1님이 접속하였습니다.
노말유저1 >> 어드민: 노말유저1님이 접속하였습니다.
노말유저2 >> 익명유저: 노말유저2님이 접속하였습니다.
노말유저2 >> 어드민: 노말유저2님이 접속하였습니다.
노말유저2 >> 노말유저1: 노말유저2님이 접속하였습니다.
노말유저3 >> 익명유저: 노말유저3님이 접속하였습니다.
노말유저3 >> 어드민: 노말유저3님이 접속하였습니다.
노말유저3 >> 노말유저1: 노말유저3님이 접속하였습니다.
노말유저3 >> 노말유저2: 노말유저3님이 접속하였습니다.
========= 유저1 메시지 =========
노말유저1 >> 익명유저: 안녕하세요!
노말유저1 >> 어드민: 안녕하세요!
노말유저1 >> 노말유저2: 안녕하세요!
노말유저1 >> 노말유저3: 안녕하세요!
========= 유저1 <-> 유저2 =========
노말유저2 >> 노말유저1: 안녕 user2야
노말유저1 >> 노말유저2: 반가워 suer1아
========= 어드민 공지 =========
어드민 >> 익명유저: [ADMIN] **반가워요**
어드민 >> 노말유저1: [ADMIN] **반가워요**
어드민 >> 노말유저2: [ADMIN] **반가워요**
어드민 >> 노말유저3: [ADMIN] **반가워요**
========= 추방하기 =========
어드민 >> 익명유저: [ADMIN] **노말유저2를 도배의 원인으로 추방합니다.**
어드민 >> 노말유저1: [ADMIN] **노말유저2를 도배의 원인으로 추방합니다.**
어드민 >> 노말유저2: [ADMIN] **노말유저2를 도배의 원인으로 추방합니다.**
어드민 >> 노말유저3: [ADMIN] **노말유저2를 도배의 원인으로 추방합니다.**
노말유저2 >> 익명유저: 노말유저2님이 퇴장하였습니다.
노말유저2 >> 어드민: 노말유저2님이 퇴장하였습니다.
노말유저2 >> 노말유저1: 노말유저2님이 퇴장하였습니다.
노말유저2 >> 노말유저3: 노말유저2님이 퇴장하였습니다.
========= 익명유저 -> 유저1 =========
익명 유저는 메시지를 전송할 수 없습니다.

보기에는 복잡해보이지만 각각 따로 받는다고 생각하면 된다.

반응형