중재자(Mediator) 패턴
실생활 사례
아파트 주민 사이에 어떤일이 생겼을 때 관리 사무소(Mediator)를 통해 해결
비행기(n) 이륙, 착륙 관련 소통 관제탑(Mediator)를 통한다.
여러 객체들이 소통하는 방법을 캡슐화하는 패턴
여러 컴포넌트간의 결합도를 중재자를 통해 낮출 수 있다.
중재자 패턴은 여러 객체가 직접 소통하는 대신, 중재자 역할을 하는 객체를 통해 간접적으로 소통하게 하는 디자인 패턴이다. 이 패턴을 사용하면 컴포넌트 간의 직접적인 의존성을 낮추고 결합도를 줄일 수 있다.
컴포넌트 A, B 서로가 직접 참조하는 대신, 이들이 중재자인 Mediator 객체를 통해 필요한 메시지나 명령을 주고받게 설정하는 거다.
이렇게 하면 컴포넌트는 다른 컴포넌트들의 구체적인 구현에 대해 몰라도 되기 떄문에 더 유연하게 변경할 수 있고, 테스트하기 쉬워진다.
해당 패턴의 그림은 예시이다.
어떤 디자인 패턴이든 구조는 정해져 있지 않다.
중요한 것은 해결하는 문제와 방식이다.
인터페이스 Mediator가 없이 ConcreteMediator만 존재할 수도 있고
Colleaue(각 컴포넌트)들 또한 공통 인터페이스를 상속하는 구조가 아닐 수 있다.
중재자(Mediator) 패턴 before
public class CleaningService {
public void clean(Gym gym){
System.out.println("clean " + gym);
}
public void clean(Restaurant restaurant){
System.out.println("clean " + restaurant);
}
public void getTower(Guest guest, int numberOfTower) {
System.out.println(numberOfTower + " towers to" + guest);
}
}
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);
}
}
public class Gym {
private CleaningService cleaningService = new CleaningService();
public void clean(){
cleaningService.clean(this);
}
}
public class Restaurant {
private CleaningService cleaningService = new CleaningService();
public void dinner(Guest guest){
System.out.println("dinner " + guest);
}
public void clean(){
cleaningService.clean(this);
}
}
현재 4가지 컴포넌트(Colleague)들이 서로를 직접 참조하고 있다.
이런식으로 하면 뭐가 문제일까?
컴포넌트 간의 결합도가 높이진다.
유연성이 부족하다
클래스 간의 직접적인 참조가 많아지면 새로운 컴포넌트를 추가하거나 기존 컴포넌트를 변경할 때
영향 범위가 넓어진다.
예를 들어, CleaningService를 수정하거나 다른 타입의 CleaningService로 교체하려면 Guest, Restaurant, Gym 클래스 또한 수정해야 한다.
테스트의 어려움
각 클래스가 서로 직접 참조하고 있기 때문에 독립적인 테스트가 어렵다.
테스트에서 각 컴포넌트가 필요한 서비스를 주입하거나 목 객체로 대체할 수 없다면 의존 관계를 설정하는 과정이 번거로워진다.
확장성 제한
각 클래스가 특정 컴포넌트를 직접 참조하므로, 새로운 요구 사항이 생길 때 변경 범위가 넓어지고 확장성이 떨어진다.
예를 들어, CleaningService 외에 LaundryService를 추가한다면, 각 클래스에 새로 추가된 LaundryService를 참조하는 코드가 필요하다.
중재자 패턴을 통한 해결
중재자 패턴을 사용하여 각 컴포넌트가 CleaningService나 다른 컴포넌트를 직접 참조하지 않고, 대신 중재자(Mediator) 클래스에 의존하도록 하면 이러한 문제를 해결할 수 있다.
중재자가 각 컴포넌트 간의 통신을 관리하고 조정함으로써, 컴포넌트 간의 결합도를 줄이고 코드의 유연성과 유지보수성을 높일 수 있다.
중재자(Mediator) 패턴 after
public class FrontDesk {
private CleaningService cleaningService = new CleaningService();
private Restaurant restaurant = new Restaurant();
public void getTowers(Guest guest, int numberOfTowers) {
// 여기서 Guest 넘기면 안 된다. -> 최소한의 정보만 넘기는 게 핵심
cleaningService.getTowers(guest.getId(), numberOfTowers);
}
public String getRoomNumber(Integer guestId) {
return "1111";
}
public void dinner(Guest guest, LocalDateTime dateTime) {
restaurant.dinner(guest.getId(), dateTime);
}
}
@Getter @Setter
public class Guest {
private Integer id;
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(int numberOfTowers){
frontDesk.getTowers(this, numberOfTowers);
}
public void dinner(LocalDateTime dateTime){
this.frontDesk.dinner(this, dateTime);
}
}
public class CleaningService {
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(Integer guestId, int numberOfTowers) {
String roomNumber = this.frontDesk.getRoomNumber(guestId);
System.out.println("provide " + numberOfTowers + " to " + roomNumber);
}
}
public class Restaurant {
public void dinner(Integer id, LocalDateTime dateTime) {
}
}
각 Colleague들은 이제 서로를 직접 참조하는 게 아닌 FrontDesk(Mediator)만 의존을 한다.
중재자 패턴을 적용해서 before에서 보였던 문제점을 해결하였다.
이 패턴의 핵심은 Mediator가 모든 의존성을 관리하는 것이다.
중재자(Mediator) 패턴 정리
장점
컴포넌트 코드를 변경하지 않고 새로운 중재자를 만들어 사용할 수 있다.
예시 코드에서는 불가능하지만 중재자를 인터페이스로 설계하고 각 구체 중재자를 통해서 확장이 가능하다.
각각의 컴포넌트 코드를 보다 간결하게 유지할 수 있다.
단점
중재자 역할을 하는 클래스의 복잡도와 결합도가 증가한다
참고자료
'Computer Sience > Desgin Pattern' 카테고리의 다른 글
[Design Pattern] 옵저버(Observer) 패턴 (0) | 2024.11.25 |
---|---|
[Design Pattern] 메멘토(Memento) 패턴 (1) | 2024.11.07 |
[Design Pattern] 이터레이터(Iterator) 패턴 (2) | 2024.10.31 |
[Design Pattern] 인터프리터(Interpreter) 패턴 (0) | 2024.10.31 |
[Design Pattern] 커맨드(Command) 패턴 (1) | 2024.10.31 |