객체간의 종속성을 줄일 수 있는 행동관련 디자인 패턴. 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 패턴
모든 사람에게 일일히 물어보는 상황을 줄이기 위해서 중간에 누가 한번에 알려주는 역할만 하게끔 하는 패턴
1. 어떤 경우에 사용되나
현실 세계에서 항공기 중재를 관제탑에서 관리한다 하면, 모든 비행기가 자기 주변의 비행기를 교신하면서 이동한다면, 복잡하고 어떻게 이동해야할지 많이 혼란스럽겠지만. 굳이 그럴 필요 없이 중재자인 관제탑에서 모든 책임을 지고 각 비행기에 명령을 할당하면, 훨씬 편하게 이동할 수 있을 것이다.
각 컴포넌트간의 의존성이 섞이는 경우가 존재하는데, 이런 경우를 방지하기 위해서는
아예 모든 의존성을 Dialog에서 가지고 각 패턴마다 어떤 상황에 따라서 특정 행동을 할 수 있게끔 각 컴포넌트별로 이벤트를 전달하거나 값을 전달하는 방식으로 처리하면 훨씬 관리할 것들이 줄어서 편리하다.
2. 예제 코드
해당 코드는 baeldung의 예제 코드를 참고하여 각색한 코드이다.
팬, 전원장치와 버튼을 이용하여 냉각 장치를 구축한다고 할때 버튼을 누르면 팬이 켜지거나 꺼지고, 팬을 켜기전에 전원장치를 켜야하는 요구사항이 존재할 것이다. 이를 아래와 같이 구현할 수 있다.
위 코드의 문제점은 Fan이 Button, PowerSupplier을 가지고 있고 Button이 Fan을 가지는 등 서로가 강하게 결합되어 있는 상태이다.
이로 인해 Fan이 PowerSupplier를 가지고 있다보니 Circulator에 PowerSupplier를 다른 객체로 추가하고자 한다면 Button도 PowerSupplier의 의존성이 생기는 문제가 발생해 확장이 어렵다. 또한, Button이 Circulator뿐만이 아닌 다른 시스템에서 사용하고자 한다면 Fan에 의존되어있다보니 매우 어렵다.
위 코드를 보면 System이라는 인터페이스를 CirculatorSystem이 상속받고 Fan, Button, PowerSupplier은 System 하나만 가지고있다. 여러 협업을 할 때는 System 객체를 이용해서 서로를 호출해서 사용하는 것이다.
여기서 중요한 것은 Colleague( Fan, Button, PowerSupplier)들은 오로지 Mediator(System)만을 가진다는 점이다. 이런 구조를 가질 경우 다른 Colleague를 추가하기도 쉬워지고 각각의 Colleague를 다른 System 구현체에서 재활용할 수 있다.
publicclassPanel {privateSystem system;privateboolean isOn =false;publicPanel(System system) {this.system= system; }publicvoidturnOn() {system.start(); isOn =true; }publicvoidturnOff() { isOn =false;system.stop(); }publicbooleanisOn() {return isOn; }}publicclassTvSystemimplementsSystem{privatefinalPanel panel =newPanel(this);privatefinalButton button =newButton(this);privatefinalPowerSupplier powerSupplier =newPowerSupplier();publicvoidpress() {if (panel.isOn()) {java.lang.System.out.print("TV ");panel.turnOff(); } else {java.lang.System.out.print("TV ");panel.turnOn(); } }publicvoidstart() {powerSupplier.turnOn(); }publicvoidstop() {powerSupplier.turnOff(); }publicButtongetButton() {return button; }publicPanelgetPanel() {return panel; }publicPowerSuppliergetPowerSupplier() {return powerSupplier; }}publicclassTv {privateTvSystem tvSystem =newTvSystem();publicButtongetButton(){returntvSystem.getButton(); }}publicclassClient {publicstaticvoidmain(String[] args) {Tv tv =newTv();tv.getButton().press();tv.getButton().press(); }}//printTV 파워 onTV 파워 off
3. 적용할 수 있는 상황
클래스가 다른 클래스와 밀접하게 연결되어 있어 일부 클래스를 변경하기 어려운 경우
커플링이 심해 클래스를 다른 프로그램에서 재사용하기 어려운 경우
4. 자바, 스프링 사용처
JAVA
ExecutorService
Executor
Spring
DispatcherServlet → 서로 간의 연결관계가 없는데, 서로를 연관지어서 관리하고 요청을 받아서 처리할 수 있는 패턴
HandlerMapping, HandlerAdapter처럼, 서로간의 의존성이 떨어져서 코드 작성이 유리해지는 방향으로 좋아졌다.
mapping을 통해서 adaptor를 찾고 ... 복잡한 방향으로 갈 수도 있었으나, 두 개를 엮어서 갈 수 있었겠지만, 그런 의존성들을 DispatcherServlet으로 몰아서 의존성을 다 한쪽으로 주고, 각 컴포넌트들은 어렵지 않게 가져가는 방향으로 설계되었다고 봐도 좋아보인다.
5. 장단점
장점
SRP
OCP
결합도 감소
재사용성 증가
단점
GOD Object로 변질 →God Object? 많은 객체를 참조하거나 관련되지 않거나 분류가 되지 않은 메서드가 많은 객체로 안티패턴이나 Code smell의 예