행동 관련 : 중재자 패턴 (Mediator Pattern)

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다. 

     


    중재자 패턴

    • GOF : 여러 객체들이 소통하는 방법을 캡슐화 하는 패턴
      • 여러 컴포넌트(Collegue) 간의 결합도를 중재자를 통해 낮출 수 있음.
      • 이 패턴에서는 구조가 중요한 것은 아님. 
    • 여러 객체들 간의 의사소통 하는 방법을 추상화 시켜서 객체들 간의 의존성 결합도를 낮춘다.
      • 각 컴포넌트들이 직접 의사소통을 하는 것은 강하게 결합된 것임.  → 코드의 변경 / 테스트 / 재사용이 어려워짐. 
      • 예를 들어 비행기들끼리 착륙할 때 '관제탑'이라는 '중재자'를 통해서 연락함. 
    • Component
      • Mediator : 모든 Colleague로부터 요청을 받아들이고 이것을 처리할 객체임. 다른 클래스에 대한 의존성을 모두 몰아놓음.
      • Colleague : 모든 Colleague는 Mediator 인터페이스를 참조함. (Collegue가 Collegue를 절대로 직접 참조하지는 않음) 
    •  Mediator가 Collegue를 호출할 때는 다른 Collegue를 구체적으로 넘기지 않고, 요청을 처리하는데 필요한 최소한의 정보만 넘기도록 한다. 필요한 정보만을 넘겨서 의존성을 낮추는 것이다.

     


    패턴이 필요한 코드

    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);
        }
    }

    위 코드를 살펴보면 각 클래스가 서로 다른 클래스를 바로 참조하는 것이 많은 것을 알 수 있다. 

    • CleaningService 클래스는 3개의 클래스를 참조하고 있음. 
    • 현재는 클리닝 서비스는 체육관, 호텔, 식당을 모두 참조하고 있음. (굉장히 얽혀있음) 
    • 만약 클리닝 서비스에 새로운 기능을 추가하거나, 코드를 변경할 때 굉장히 복잡해진다. 
    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);
        }
    
    }
    • Guest 클래스는 2개의 클래스를 참조하고 있음. 

    각 클래스의 의존 관계를 살펴보면 다음과 같다. 각각의 클래스들이 서로를 참조하고 있으며, 거미줄처럼 촘촘히 참조하고 있다. 이렇게 강하게 결합한 클래스가 많은 경우에는 각 클래스들에 새로운 기능을 추가하거나 코드를 수정하기가 쉽지 않다. 

    이런 경우에는 중재자 패턴을 적용해서 '강하게 결합한 의존성'을 하나의 클래스로 몰아주는 방법으로 코드를 개선해 볼 수도 있다. 

     


    디자인 패턴 적용하기

    우선 디자인 패턴을 적용한 결과를 살펴보면 다음과 같이 개선된다. FrontDesk라는 '중재자' 클래스를 하나 만들고, FrontDesk만 의존성을 가져가도록 하는 것이다. 관리하기 한결 편리한 의존 관계가 형성된 것을 볼 수 있다. 

    코드를 개선하기 위해 다음과 같이 작성한다.

    1. 중재자로 FrontDesk 클래스를 추가한다.
    2. Restaurant, Guest, CleaninngService는 다른 Colleague를 참조하지 않고, FrontDesk만 참조하도록 한다.
    3. FrontDesk는 각 Collegue가 호출될 때 마다, 해당 요청을 적절한 Collegue 쪽으로 디스패치한다.
    4. FrontDesk가 Dispatch 할 때는 최소한의 정보만 넘겨준다. (예를 들어 CleaningService에 Guest 객체를 넘기지 않도록 한다) 
    // 중재자
    public class FrontDesk {
    
        private final CleaningService cleaningService;
        private final Restaurant restaurant;
    
        public FrontDesk(CleaningService cleaningService, Restaurant restaurant) {
            this.cleaningService = cleaningService;
            this.restaurant = restaurant;
        }
    
        public void getTower(Guest guest, int numberOfTower) {
            cleaningService.getTower(guest.getId(), numberOfTower);
        }
    
        public void dinner(Guest guest) {
            restaurant.dinner(guest.getId());
        }
    
        public void clean(Restaurant restaurant) {
            cleaningService.clean(restaurant.getRestaurantId());
        }
    }
    • 중재자인 FrontDesk에 모든 객체에 대한 의존성을 몰아준다. (Guest, Restaurant, CleaningService) 
    • 중재자는 각 Collegue를 호출할 때 최소한으로 필요한 정보만 넘겨준다. (guest.getId() 같은 것) 
    @Getter
    public class Guest {
        private final Long id;
        private final FrontDesk frontDesk;
    
        public Guest(Long id, FrontDesk frontDesk) {
            this.id = id;
            this.frontDesk = frontDesk;
        }
    
        public void dinner() {
            frontDesk.dinner(this);
        }
    
        public void getTower(int numberOfTower) {
            frontDesk.getTower(this, numberOfTower);
        }
    
    }

    각 Collegue는 FrontDesk에게 필요한 기능을 요청 처리한다. 절대로 다른 Collegue를 참조하지 않도록 한다. 

     


    중재자 패턴의 장/단점

    • 장점
      • 컴포넌트 코드(Collegue)를 변경하지 않고 새로운 중재자를 만들어 사용할 수 있다.
        • 인터페이스에 의존하는 경우라면 맞음.
      • 각각의 컴포넌트 코드를 보다 간결하게 유지할 수 있다. 
        • 클리닝 서비스의 새로운 코드를 추가하거나 할 때, 이전과 달리 우리는 FrontDesk와의 의사소통만 생각하면 된다. 의존성이 줄어져서 좀 더 간결해졌다.
    • 단점
      • 중재자 역할을 하는 클래스의 복잡도와 결합도가 증가한다. 
        • 중재자 클래스의 테스트 + 변경이 어려워짐.

    만약 Collegue가 Mediatotor 인터페이스를 참조하고 있다면, 새로운 Mediator가 추가되어도 Collegue에는 영향이 가지 않는다. 다만 위의 코드에서 사용한 FrontDesk는 구체 클래스이기 때문에 해당되지는 않는다. 

    각각의 컴포넌트(Collegue)들이 가지고 있는 의존성이 적기 때문에 상대적으로 Component 들은 덜 복잡한 클래스가 된다. 따라서 테스트 하기 쉽고, 수정하기 쉽고 읽기 편리하다. 

    이런 장점은 의존성이 모두 중재자로 몰려있기 때문에 획득할 수 있는데, 바꿔 이야기하면 중재자에 많은 의존성이 존재해서 중재자 클래스는 테스트 하기 어려워진다는 단점이 있다. 

    조삼모사인 디자인 패턴일 수 있지만 장/단점의 트레이드 오프를 고려해보고 적용해도 괜찮을 것 같다. 

    댓글

    Designed by JB FACTORY