중재자 패턴

GoF의 디자인패턴 중 행동의 중재자 패턴을 Java 로 정리한 글

객체간의 종속성을 줄일 수 있는 행동관련 디자인 패턴. 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 패턴

모든 사람에게 일일히 물어보는 상황을 줄이기 위해서 중간에 누가 한번에 알려주는 역할만 하게끔 하는 패턴


1. 어떤 경우에 사용되나

현실 세계에서 항공기 중재관제탑에서 관리한다 하면, 모든 비행기가 자기 주변의 비행기를 교신하면서 이동한다면, 복잡하고 어떻게 이동해야할지 많이 혼란스럽겠지만. 굳이 그럴 필요 없이 중재자관제탑에서 모든 책임을 지고 각 비행기에 명령을 할당하면, 훨씬 편하게 이동할 수 있을 것이다.

각 컴포넌트간의 의존성이 섞이는 경우가 존재하는데, 이런 경우를 방지하기 위해서는

아예 모든 의존성을 Dialog에서 가지고 각 패턴마다 어떤 상황에 따라서 특정 행동을 할 수 있게끔 각 컴포넌트별로 이벤트를 전달하거나 값을 전달하는 방식으로 처리하면 훨씬 관리할 것들이 줄어서 편리하다.


2. 예제 코드

해당 코드는 baeldung의 예제 코드를 참고하여 각색한 코드이다.

, 전원장치버튼을 이용하여 냉각 장치를 구축한다고 할때 버튼을 누르면 이 켜지거나 꺼지고, 을 켜기전에 전원장치를 켜야하는 요구사항이 존재할 것이다. 이를 아래와 같이 구현할 수 있다.

중재자 패턴 적용전 코드

//팬 클래스
public class Fan {
    private final Button button;
    private final PowerSupplier powerSupplier = new PowerSupplier();
    private boolean isOn = false;

    public Fan() {
        this.button = new Button(this);
    }

    public Fan(Button button) {
        this.button = button;
    }

    public void turnOn() {
        isOn = true;
        powerSupplier.turnOn();
    }

    public void turnOff() {
        isOn = false;
        powerSupplier.turnOff();
    }

    public boolean isOn() {
        return isOn;
    }

    public Button getButton() {
        return button;
    }

    public PowerSupplier getPowerSupplier() {
        return powerSupplier;
    }
}

//버튼 클래스
public class Button {
    private Fan fan;

    public Button() {
        this.fan = new Fan(this);
    }

    public Button(Fan fan) {
        this.fan = fan;
    }

    public void press(){
        if(fan.isOn()){
            fan.turnOff();
        } else {
            fan.turnOn();
        }
    }
}

//전원 장치 클래스
public class PowerSupplier {
    public void turnOn() {
        System.out.println("파워 on");
    }

    public void turnOff() {
        System.out.println("파워 off");
    }
}

//냉각 장치 클래스
public class Circulator {
    private Fan fan;
    private Button button;
    private PowerSupplier powerSupplier;

    public Circulator() {
        this.fan = new Fan();
        this.button = fan.getButton();
        this.powerSupplier = fan.getPowerSupplier();
    }

    public Fan getFan() {
        return fan;
    }

    public Button getButton() {
        return button;
    }

    public PowerSupplier getPowerSupplier() {
        return powerSupplier;
    }
}

//클라이언트
public class Client {
    public static void main(String[] args) {
        Circulator circulator = new Circulator();
        Button button = circulator.getButton();
        button.press();
        button.press();
    }
}

//print
파워 on
파워 off

위 코드의 문제점은 FanButton, PowerSupplier을 가지고 있고 ButtonFan을 가지는 등 서로가 강하게 결합되어 있는 상태이다.

이로 인해 FanPowerSupplier를 가지고 있다보니 CirculatorPowerSupplier를 다른 객체로 추가하고자 한다면 ButtonPowerSupplier의 의존성이 생기는 문제가 발생해 확장이 어렵다. 또한, ButtonCirculator뿐만이 아닌 다른 시스템에서 사용하고자 한다면 Fan에 의존되어있다보니 매우 어렵다.

이제 이러한 상황을 중재자 패턴을 이용해 해결해보자.

중재자 패턴 적용후 코드

public class Fan {
    private System system;
    private boolean isOn = false;

    public Fan(System system) {
        this.system = system;
    }

    public void turnOn() {
        system.start();
        isOn = true;
    }

    public void turnOff() {
        isOn = false;
        system.stop();
    }

    public boolean isOn() {
        return isOn;
    }
}

public class Button {
    private final System system;

    public Button(System system) {
        this.system = system;
    }

    public void press() {
        system.press();
    }
}

public class PowerSupplier {

    public void turnOn() {
        java.lang.System.out.println("파워 on");
    }

    public void turnOff() {
        java.lang.System.out.println("파워 off");
    }
}

public interface System {
    void press();
    void start();
    void stop();
}
}

public class CirculatorSystem implements System {
    private final Fan fan = new Fan(this);
    private final Button button = new Button(this);
    private final PowerSupplier powerSupplier = new PowerSupplier();

    public void press() {
        if (fan.isOn()) {
            java.lang.System.out.print("써큘레이터 ");
            fan.turnOff();
        } else {
            java.lang.System.out.print("써큘레이터 ");
            fan.turnOn();
        }
    }

    public void start() {
        powerSupplier.turnOn();
    }

    public void stop() {
        powerSupplier.turnOff();
    }

    public Button getButton() {
        return button;
    }

    public Fan getFan() {
        return fan;
    }

    public PowerSupplier getPowerSupplier() {
        return powerSupplier;
    }
}

public class Client {
    public static void main(String[] args) {
        Circulator circulator = new Circulator();
        Button button = circulator.getButton();
        button.press();
        button.press();
    }
}
//print
써큘레이터 파워 on
써큘레이터 파워 off

위 코드를 보면 System이라는 인터페이스를 CirculatorSystem이 상속받고 Fan, Button, PowerSupplierSystem 하나만 가지고있다. 여러 협업을 할 때는 System 객체를 이용해서 서로를 호출해서 사용하는 것이다.

여기서 중요한 것은 Colleague( Fan, Button, PowerSupplier)들은 오로지 Mediator(System)만을 가진다는 점이다. 이런 구조를 가질 경우 다른 Colleague를 추가하기도 쉬워지고 각각의 Colleague를 다른 System 구현체에서 재활용할 수 있다.

public class Panel {
        private System system;
        private boolean isOn = false;

        public Panel(System system) {
            this.system = system;
        }

        public void turnOn() {
            system.start();
            isOn = true;
        }

        public void turnOff() {
            isOn = false;
            system.stop();
        }

        public boolean isOn() {
            return isOn;
        }
}

public class TvSystem implements System{
    private final Panel panel = new Panel(this);
    private final Button button = new Button(this);
    private final PowerSupplier powerSupplier = new PowerSupplier();

    public void press() {
        if (panel.isOn()) {
            java.lang.System.out.print("TV ");
            panel.turnOff();
        } else {
            java.lang.System.out.print("TV ");
            panel.turnOn();
        }
    }

    public void start() {
        powerSupplier.turnOn();
    }

    public void stop() {
        powerSupplier.turnOff();
    }

    public Button getButton() {
        return button;
    }

    public Panel getPanel() {
        return panel;
    }

    public PowerSupplier getPowerSupplier() {
        return powerSupplier;
    }
}

public class Tv {
    private TvSystem tvSystem = new TvSystem();

    public Button getButton(){
        return tvSystem.getButton();
    }
}
public class Client {
    public static void main(String[] args) {
        Tv tv = new Tv();
        tv.getButton().press();
        tv.getButton().press();
    }
}

//print
TV 파워 on
TV 파워 off

3. 적용할 수 있는 상황

  1. 클래스가 다른 클래스와 밀접하게 연결되어 있어 일부 클래스를 변경하기 어려운 경우

  2. 커플링이 심해 클래스를 다른 프로그램에서 재사용하기 어려운 경우


4. 자바, 스프링 사용처

JAVA

  • ExecutorService

  • Executor

Spring

  • DispatcherServlet → 서로 간의 연결관계가 없는데, 서로를 연관지어서 관리하고 요청을 받아서 처리할 수 있는 패턴

HandlerMapping, HandlerAdapter처럼, 서로간의 의존성이 떨어져서 코드 작성이 유리해지는 방향으로 좋아졌다.

mapping을 통해서 adaptor를 찾고 ... 복잡한 방향으로 갈 수도 있었으나, 두 개를 엮어서 갈 수 있었겠지만, 그런 의존성들을 DispatcherServlet으로 몰아서 의존성을 다 한쪽으로 주고, 각 컴포넌트들은 어렵지 않게 가져가는 방향으로 설계되었다고 봐도 좋아보인다.


5. 장단점

장점

  • SRP

  • OCP

  • 결합도 감소

  • 재사용성 증가

단점

  • GOD Object로 변질 →God Object? 많은 객체를 참조하거나 관련되지 않거나 분류가 되지 않은 메서드가 많은 객체로 안티패턴이나 Code smell의 예


Last updated