중재자(Mediator) 패턴은 여러 객체들 간의 의사소통하는 방법을 추상화시켜서 여러 객채들 간의 결합도를 낮추는 방법입니다.
위 그림의 구조를 살펴보면 중재자역할을 하는 Mediator인터페이스가 있고 모든 Colleague들은 Mediator라는 인터페이스를 참조하고 있습니다. Mediator의 구현체인 ConcreteMediator가 ColleagueA, ColleagueB를 참조하고 있습니다. 여기서 중요한점은 Colleague들끼리 직접 참조하고 있지 않다는 점입니다.
중재자 패턴이 필요해지는 예시 코드를 살펴보겠습니다. 호텔을 코드화 시켰습니다. 호텔에는 Gym, CleaningService, Guest, Restaurant가 있다고 가정하겠습니다.
Gym은 CleaningService를 의존하고 있습니다.
public class Gym {
private CleaningService cleaningService;
public void clean() {
cleaningService.clean(this);
}
}
CleaningService에는 Gym, Guest, Restaurant를 인자로 받아서 처리하는 로직들이 있습니다.
public class CleaningService {
public void clean(Gym gym) {
System.out.println("clean " + gym);
}
public void getTower(Guest guest, int numberOfTower) {
System.out.println(numberOfTower + " towers to " + guest);
}
public void clean(Restaurant restaurant) {
System.out.println("clean " + restaurant);
}
}
Guest에는 Restaurant와 CleaningService를 사용합니다.
public class Guest {
private Restaurant restaurant = new Restaurant();
private CleaningService cleaningService = new CleaningService();
public void dinner() {
restaurant.dinner(this);
}
public void getTower(int numberOfTower) {
cleaningService.getTower(this, numberOfTower);
}
}
Restaurant은 CleaningService를 사용합니다.
public class Restaurant {
private CleaningService cleaningService = new CleaningService();
public void dinner(Guest guest) {
System.out.println("dinner " + guest);
}
public void clean() {
cleaningService.clean(this);
}
}
이렇게 선언한 클래스들의 관계를 보면 얽히고 설켜 있다는것을 알 수 있습니다. 이렇게 뒤죽박죽한 관계는 하나의 코드변경에 그 코드를 의존하는 다른 코드들을 변경해야하고 재사용성도 떨어집니다. 그리고 테스트코드를 준비할때도 필요한 객체들이 모두 필요해서 테스트하기가 까다롭게 됩니다. 이럴때 중재자(Mediator) 패턴을 적용해서 문제를 해결할 수 있습니다.
먼저 Mediator역할로 FrontDesk클래스를 선언합니다. 그 다음 Colleague인 Guest가 수건을 요청하고 싶습니다. 이럴때 Mediator인 FrontDesk로 수건을 요청하는 getTowers메서드를 정의합니다. 인자로 수건의 갯수를 전달받도록 합니다. 또 Guest는 Restaurant에서 저녁을 먹고싶습니다. 이것도 마찬가지로 Mediator인 FrontDesk에 dinner메서드를 정의합니다. Mediator에 모든 Colleague를 가지고 있어야 하기때문에 CleaningService와 Restaurant를 의존하고 있습니다.
public class FrontDesk {
private CleaningService cleaningService = new CleaningService();
private Restaurant restaurant = new Restaurant();
public void getTowers(Guest guest, int numberOfTowers) {
cleaningService.getTowers(guest.getId(), numberOfTowers);
}
public void dinner(Guest guest, LocalDateTime dateTime) {
restaurant.dinner(guest.getId(), dateTime);
}
}
public class Guest {
private Integer id;
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(int numberOfTowers) {
this.frontDesk.getTowers(this, numberOfTowers);
}
private void dinner(LocalDateTime dateTime) {
this.frontDesk.dinner(this, dateTime);
}
// getter, setter
}
Colleague인 CleaningService에서 guestId에 해당하는 roomNumber를 물어보려면 Mediator인 FrontDesk에서 getRoomNumberFor메서드를 선언합니다.
public class CleaningService {
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(Integer guestId, int numberOfTowers) {
String roomNumber = this.frontDesk.getRoomNumberFor(guestId);
System.out.println("provide " + numberOfTowers + " to " + roomNumber);
}
}
public class FrontDesk {
// 코드 생략
public String getRoomNumberFor(Integer guestId) {
return "1111";
}
}
이렇게 Guest, CleaningService와 같은 Colleague들은 서로 의존하지 않고 Mediator인 FrontDesk만 의존하게 됩니다.
중재자(Mediator) 패턴을 사용하면 새로운 중재자를 생성해서 각 컴포넌트들이 서로 의존하지 않고 Mediator를 통해서만 요청을 할 수 있게 코드를 간결하게 유지할 수 있습니다. 하지만 모든 의존성을 가지고있는 Mediator의 복잡도와 결합도는 증가하게 됩니다.