중재자 패턴이란, 객체간의 복잡한 의사소통을 최소화하기 위해 사용하는 패턴이다. 복잡한 의사소통?? 예를 들면 객체간 서로 메시지를 교환하는 데 객체간 결합이 많아지면 결합도가 커지고 테스트가 어려워짐과 동시에 유지보수하기가 어려워진다. 그래서 중재자 객체를 만들어 서로 소통하는 객체들은 별다른 의존성 없이 중재자만 결합함으로써 결합도를 줄일 수 있다. 물론 중재자의 결합도는 엄청나게 커지는 단점이 존재한다.
실제 중재자 패턴은 JAVA Spring의 Dispatcher Servlet 에서도 사용된다. Spring 이전에는 각각의 기능을 가진 Servlet들이 서로 소통하며 구성해왔다. 그러나 servlet이 많아질수록 의사소통이 굉장히 복잡해졌고 이를 하나의 객체에서 서로간의 servlet이 소통하도록 만들어보자 해서 생겨난 것이 Spring이다. 이를 통해 개발자는 실제 비즈니스 로직에서 의존성을 줄이고 조금 더 편하게 API를 개발할 수 있다.
위의 UML을 살펴보면 각각의 colleague 객체들은 mediator를 통해 요청하고 mediator에서 각각의 colleague로 응답함을 볼 수 있다.
예제에는 Colleague 인터페이스 하나만 있지만 여러 인터페이스가 있어도 상관없다. 이 디자인 패턴의 핵심은 각각의 객체들이 소통함에 있어서 중재자 객체를 통해 결합도를 줄이는 것이 목적이다.
손님이 식당에서 음식을 주문하고 타월을 요청하는 예제를 살펴보자.
간단하게 도메인을 구성해보았다.
이를 코드로 바꿔서 분석해보자.
public interface CleaningService {
void clean(Chef restaurant);
Towel makeTowel();
}
public interface Guest {
void getTowels();
void diner(FoodMenu foodMenu);
}
public interface Chef {
void clean();
Food makeFood(FoodMenu foodMenu);
}
해당 도메인의 인터페이스를 구성해보았다. 이제 행위를 정의했으니 실제 구현을 해보자
// 각각 객체의 구현체
public class CleaningServiceImpl implements CleaningService {
@Override
public void clean(Chef restaurant) {
System.out.println("your seat is clean!!!");
}
@Override
public Towel makeTowel() {
return new Towel("스포츠 타월");
}
}
public class User implements Guest {
private final CleaningService cleaningService = new CleaningServiceImpl();
private final Chef restaurant = new AceChef();
@Override
public void getTowels() {
Towel towel = cleaningService.makeTowel();
if (towel != null) {
System.out.println("Oh I get towel");
return;
}
System.out.println("I don't receive towel");
}
@Override
public void diner(FoodMenu foodMenu) {
Food food = restaurant.makeFood(foodMenu);
System.out.printf("My dinner is %s\n", food);
}
}
public class AceChef implements Chef {
private final CleaningService cleaningService = new CleaningServiceImpl();
@Override
public void clean() {
cleaningService.clean(this);
}
@Override
public Food makeFood(FoodMenu foodMenu) {
return Food.of(foodMenu);
}
}
// 소통에 사욜할 객체들
public enum FoodMenu {
COFFEE("커피"),
SPAGHETTI("스파게티"),
BACON("베이컨"),
FISH_AND_CHEEPS("피시 앤 칩스");
private final String name;
FoodMenu(String name) {
this.name = name;
}
public String getFoodName() {
return name;
}
}
public class Food {
private final String name;
private Food(String name) {
this.name = name;
}
public static Food of(FoodMenu foodMenu) {
return new Food(foodMenu.getFoodName());
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
'}';
}
}
public class Towel {
private final String name;
public Towel(String name) {
this.name = name;
}
@Override
public String toString() {
return "Towel{" +
"name='" + name + '\'' +
'}';
}
}
여기서 예를 들어 셰프가 재료를 주문하기 위해서 마켓에 주문을 하고, cleaning service 외에 손님에게 가수 이벤트 제공 서비스도 해주고 등등, 방향성이 일정하지 않고 서로 양방향, 그리고 결합도가 높아지는 일이 발생한다면, 계속 aggreate를 추가하는 것은 유지보수하기 어렵고 코드 관리도 까다로워진다. 이 때 중재자 패턴을 활용하면 각각의 객체들은 서비스가 추가되는 말든 의존성을 신경 쓸 필요가 사라진다. 직접 확인해보자.
public interface FrontDesk {
void clean();
Towel getTowel();
Food makeFood(FoodMenu foodMenu);
}
public class DefaultFrontDesk implements FrontDesk {
private final Guest guest = new User();
private final CleaningService cleaningService = new CleaningServiceImpl();
private final Chef chef = new AceChef();
@Override
public void clean() {
cleaningService.clean(chef);
}
@Override
public Towel getTowel() {
return cleaningService.makeTowel();
}
@Override
public Food makeFood(FoodMenu foodMenu) {
return chef.makeFood(foodMenu);
}
}
각각 소통하는 객체들을 모두 FrontDesk에 넣어준다.
public class User implements Guest {
private final FrontDesk frontDesk = new DefaultFrontDesk();
@Override
public void getTowels() {
Towel towel = frontDesk.getTowel();
if (towel != null) {
System.out.println("Oh I get towel");
return;
}
System.out.println("I don't receive towel");
}
@Override
public void diner(FoodMenu foodMenu) {
Food food = frontDesk.makeFood(foodMenu);
System.out.printf("My dinner is %s\n", food);
}
}
public class AceChef implements Chef {
private final FrontDesk frontDesk = new DefaultFrontDesk();
@Override
public void clean() {
frontDesk.clean();
}
@Override
public Food makeFood(FoodMenu foodMenu) {
return Food.of(foodMenu);
}
}
마치 중재자 패턴은 프론트 데스크 직원이 여러 책임을 가진 사람들에게 달려가 요청 내용을 대신 전달해주는 것 같다. 맞다. 그게 중재자 패턴이다. 코드를 보면 guest의 경우 2개에서 하나로 줄을 것을 알 수 있다. 물론 지금과 같은 Simple한 예제에서는 중재자 패턴을 사용할 필요가 없지만, 점점 서비스가 추가된다면 의존성을 관리하기 어렵고 객체들간의 서로 메시지를 파악하는게 복잡해질 수 있다. 이럴 때 Mediator 패턴을 사용하자.