중재자 대 관찰자 객체 지향 디자인 패턴


94

나는 내 문제의 일부를 해결하기 위해 Gang Of Four를 읽고 있었고 Mediator 패턴을 발견했습니다.

이전에 일부 GUI 응용 프로그램을 만들기 위해 내 프로젝트에서 Observer 를 사용했습니다 . 나는 둘 사이에 큰 차이를 찾지 못해 약간 혼란스러워합니다. 차이점을 찾기 위해 탐색했지만 내 질문에 대한 적절한 대답을 찾을 수 없었습니다.

두 사람을 명확하게 구분하는 좋은 예를 통해 두 사람을 구별하는 데 도움이 될 수 있습니까?


5
이 질문을 (으) Programmers.StackExchange로 마이그레이션하라는 요청 은 거부되었지만 답변에 관심이 있었기 때문에 비슷한 게시물을 작성했습니다 . 흥미로운 답변을 찾을 수 있습니다. :)
Rachel

JavaScript 예제의 경우 비슷한 질문에 대한 제 답변을 볼 수 있습니다 .
Alex Pakka 2014-08-21

원래 GoF 책에서는 .NET Framework 를 사용 ChangeManager하는 Observer패턴에 대한 예제를 제공하여 구현 섹션 포인트 # 8에서 이를 해결합니다 Mediator. 보다; paginas.fe.up.pt/~aaguiar/as/gof/hires/pat5g.htm#samplecode
ROBI-Y

답변:


105

관찰자 패턴 : 개체 간의 일대 다 종속성을 정의하여 한 개체의 상태가 변경 될 때 모든 종속 항목이 자동으로 알림 및 업데이트되도록합니다.

중재자 패턴 : 개체 집합이 상호 작용하는 방식을 캡슐화하는 개체를 정의합니다. 중재자는 객체가 서로를 명시 적으로 참조하지 못하도록하여 느슨한 결합을 촉진하며 상호 작용을 독립적으로 변경할 수 있습니다.

출처 : dofactory

예:

관찰자 패턴 : 클래스 A는 0 개 이상의 O 형 관찰자를 등록 할 수 있습니다. A의 내용이 변경되면 모든 관찰자에게 알립니다.

중개자 패턴 : 클래스 X의 인스턴스 (또는 X, Y & Z)의 인스턴스가 몇 개 있고 서로 통신하기를 원합니다 (하지만 각각에 대한 명시 적 참조를 갖기를 원하지는 않습니다). other), 따라서 중개자 클래스 M을 만듭니다. X의 각 인스턴스에는 M의 공유 인스턴스에 대한 참조가 있으며이를 통해 X (또는 X, Y 및 Z)의 다른 인스턴스와 통신 할 수 있습니다.


관찰자에 대한 설명은 관찰자 패턴보다는 명령 패턴에 가까운 것 같습니다
Aun

40

Observer와 Mediator, Design Patterns, Elements of Reusable Object-Oriented Software 라는 용어를 만든 원저에서는 관찰자 패턴을 사용하여 Mediator 패턴을 구현할 수 있다고 말합니다. 그러나 동료 (Observer 패턴의 주제와 거의 동일 함)가 Mediator 클래스 또는 Mediator 인터페이스에 대한 참조를 갖도록하여 구현할 수도 있습니다.

관찰자 패턴을 사용하려는 경우가 많이 있습니다. 핵심은 객체가 다른 객체가 자신의 상태를 관찰하고 있는지 알 수 없다는 것입니다.

중재자는 좀 더 구체적이며 클래스가 직접 통신하는 것을 피하고 대신 중재자를 통해 전달합니다. 이는 통신을 처리하는 클래스로 통신을 오프로드 할 수 있도록하여 단일 책임 원칙에 도움이됩니다.

전형적인 Mediator 예제는 GUI에 있습니다. 순진한 접근 방식은 "Foo 패널이 비활성화되고 Bar 패널에"날짜를 입력하십시오 "라는 레이블이있는 경우 서버를 호출하지 마십시오."라는 버튼 클릭 이벤트에 대한 코드로 이어질 수 있습니다. 그렇지 않으면 진행하십시오. "Mediator 패턴을 사용하면"나는 단지 버튼 일 뿐이며 Foo 패널과 Bar 패널의 레이블에 대해 알고있는 지상 비즈니스가 없습니다. 따라서 서버에 전화를 걸 었는지 중재자에게 물어볼 것입니다. 지금은 괜찮습니다. "

또는 중재자가 Observer 패턴을 사용하여 구현 된 경우 버튼은 "Hey, Observer (중재자를 포함), 내 상태가 변경되었습니다 (누군가 나를 클릭했습니다). 관심이 있으면 조치를 취하십시오."라고 표시됩니다. 내 예에서는 중개자를 직접 참조하는 것보다 덜 의미가 있지만 대부분의 경우 Observer 패턴을 사용하여 Mediator를 구현하는 것이 합리적이며 Observer와 Mediator의 차이는 코드 자체의 차이보다 의도가 더 많습니다.


저는 "단일 책임 원칙"이라는 단어를 찾고있었습니다.
stdout

37

관찰자

1.없이

  • Client1 : Hey Subject , 언제 바뀌나요?

  • Client2 : 언제 제목 을 변경 했습니까? 나는 눈치 채지 못했다!

  • Client3 : 주제가 변경된 것을 알고 있습니다.

2. 함께

  • 클라이언트 는 침묵합니다.
  • 얼마 후 ...
  • 제목 : 고객 여러분 , 저는 변했습니다!

중재인

1.없이

  • Client1 : Hey Taxi1 , 어디로 데려가.
  • Client2 : Hey Taxi1 , 어디로 데려가.
  • Client1 : Hey Taxi2 , 어디로 데려가.
  • Client2 : Hey Taxi2 , 어디로 데려가.

2. 함께

  • Client1 : Hey TaxiCenter , 택시 를 타주세요 .
  • Client2 : Hey TaxiCenter , 택시 를 타주세요 .

2
귀하의 중재자의 예는 없습니다 중재자 패턴의 공장 패턴입니다
모하마드 카리미

2
@Pmpr 중개자 디자인 패턴입니다. TaxiCenter는 택시를 만들지 않고, 어떤 방법 으로든 택시를 이용할 수 있도록합니다 (아마도 TaxiCenter가 자신의 차례라고 말할 때까지 각 택시가 대기합니다)
Siva R

14

이러한 패턴은 다양한 상황에서 사용됩니다.

중개자 패턴은 종속성이있는 두 개의 하위 시스템이 있고 그 중 하나가 변경 예정인 경우에 사용되며, 다른 하나에 의존하는 시스템을 변경하지 않을 수도 있으므로 다음 중개자를 도입 할 수 있습니다. 그들 사이의 종속성을 분리하십시오. 이렇게하면 하위 시스템 중 하나가 변경 될 때 중재자를 업데이트하기 만하면됩니다.

관찰자 패턴은 클래스가 다른 클래스가 자신을 등록하고 이벤트에 대한 알림을받을 수 있도록 허용하고자 할 때 사용됩니다 (예 : ButtonListener 등).

이 두 패턴은 더 적은 결합을 허용하지만 완전히 다릅니다.


7

예를 들어 보겠습니다. 두 개의 애플리케이션을 빌드한다고 가정합니다.

  1. 채팅 응용 프로그램입니다.
  2. 응급 구급차 운영자 응용 프로그램입니다.

중재인

채팅 애플리케이션을 구축하면 mediator디자인 패턴을 선택하게됩니다 .

  • 사람들은 주어진 시간에 채팅에 참여하고 나갈 수 있으므로 두 사람이 채팅을 직접 참조하는 것은 의미가 없습니다.
  • 우리는 여전히 두 사람 사이의 의사 소통을 용이하게하고 그들이 채팅을 할 수 있도록해야합니다.

우리가 선호하는 이유는 무엇 mediator입니까? 그 정의를 살펴보십시오.

중재자 패턴을 사용하면 개체 간의 통신이 중재자 개체 내에 캡슐화됩니다. 객체는 더 이상 서로 직접 통신하지 않고 대신 중재자를 통해 통신합니다. 이렇게하면 통신 개체 간의 종속성이 줄어들어 결합이 줄어 듭니다.

마법은 어떻게 작동합니까? 먼저 채팅 중개자를 생성하고 개인 객체를 등록하여 모든 개인과 양방향 연결을 갖도록 할 것입니다 (사용자는 채팅 중개자를 사용하여 메시지를 보낼 수 있으므로 액세스 할 수 있고 채팅 중개자는 액세스 할 수 있습니다. 사람 객체의 수신 된 메소드로 인해 액세스 권한도 있습니다.)

function Person(name) {
    let self = this;
    this._name = name;
    this._chat = null;

    this._receive(from, message) {        
        console.log("{0}: '{1}'".format(from.name(), message));
    }
    this._send(to, message) {
        this._chat.message(this, to, message);
    }
    return {
        receive: (from, message) => { self._receive(from, message) },
        send: (to, message) => { self._send(to, message) },
        initChat: (chat) => { this._chat = chat; },
        name: () => { return this._name; }
    }
}


function ChatMediator() {
    let self = this;
    this._persons = [];    

    return {
        message: function (from, to, message) {
            if (self._persons.indexOf(to) > -1) {
                self._persons[to].receive(from, message);
            }
        },
        register: function (person) {
            person.initChat(self);
            self._persons.push(person);
        }
        unRegister: function (person) {
            person.initChat(null);
            delete self._persons[person.name()];
        }
    }
};

//Usage:
let chat = new ChatMediator();

let colton = new Person('Colton');
let ronan = new Person('Ronan');

chat.register(colton);
chat.register(ronan);

colton.send(colton, 'Hello there, nice to meet you');
ronan.send(ronan, 'Nice to meet you to');

colton.send(colton, 'Goodbye!');
chat.unRegister(colton);

관찰자

911 통화 애플리케이션을 구축하면 observer디자인 패턴을 선택하게됩니다 .

  • 각 구급차 observer개체는 긴급 상황이있을 때 알림을 받기를 원하므로 주소를 운전하고 도움을 줄 수 있습니다.
  • 비상 운영자 observable는 구급차의 각 정보를 계속 참조하고 observers도움이 필요할 때 (또는 이벤트 생성) 알립니다.

우리가 선호하는 이유는 무엇 observer입니까? 그 정의를 살펴보십시오.

주체라고하는 객체는 관찰자라고하는 종속 목록을 유지하고 일반적으로 메서드 중 하나를 호출하여 상태 변경을 자동으로 알립니다.

function AmbulanceObserver(name) {
    let self = this;
    this._name = name;
    this._send(address) {
        console.log(this._name + ' has been sent to the address: ' + address);
    }
    return {
        send: (address) => { self._send(address) },
        name: () => { return this._name; }
    }
}


function OperatorObservable() {
    let self = this;
    this._ambulances = [];    

    return {
        send: function (ambulance, address) {
            if (self._ambulances.indexOf(ambulance) > -1) {
                self._ambulances[ambulance].send(address);
            }
        },
        register: function (ambulance) {
            self._ambulances.push(ambulance);
        }
        unRegister: function (ambulance) {
            delete self._ambulances[ambulance.name()];
        }
    }
};

//Usage:
let operator = new OperatorObservable();

let amb111 = new AmbulanceObserver('111');
let amb112 = new AmbulanceObserver('112');

operator.register(amb111);
operator.register(amb112);

operator.send(amb111, '27010 La Sierra Lane Austin, MN 000');
operator.unRegister(amb111);

operator.send(amb112, '97011 La Sierra Lane Austin, BN 111');
operator.unRegister(amb112);

차이점들:

  1. 채팅 mediator은 사람 개체 (송신 및 수신)간에 양방향 통신 observable이 가능 하며 운영자 는 단방향 통신 만 가능합니다 (구급차 observer에게 운전하고 종료하도록 지시).
  2. 채팅 mediator은 사람 개체가 그들 사이에서 상호 작용하도록 할 수 있으며 (직접 통신이 아니더라도) 구급차 observers는 운영자 observable이벤트 에만 등록 합니다.
  3. 각 사람 개체는 채팅에 대한 참조를 가지고 mediator, 또한 채팅 mediator위격의 모든 하나 킵 참조. 구급차 observer가 운영자를 참조하지 않는 observable경우, 운영자 만이 observable모든 구급차를 참조합니다 observer.

3
마지막 부분이 도움이됩니다. 중재자와 관찰자는 모두 동일한 목표를 달성하지만 중재자는 양방향 통신을 가능하게하고 관찰자는 한 방향으로 만 작동합니다.
kiwicomb123

정확히, 다행이 도움이
샤 하르 Shokrani에게

6

둘 다 상태 변경에 대해 체계적으로 알리는 데 사용되지만 구조적으로나 의미 적으로 IMO가 약간 다릅니다.

Observer는 객체 자체에서 특정 객체의 상태 변경을 브로드 캐스트하는 데 사용됩니다. 따라서 변화는 신호를 보내는 역할도 담당하는 중앙 개체에서 발생합니다. 그러나 중재자에서 상태 변경은 모든 개체에서 발생할 수 있지만 중재자에서 브로드 캐스팅됩니다. 그래서 흐름에 차이가 있습니다. 그러나 이것이 우리의 코드 동작에 영향을 미치지 않는다고 생각합니다. 우리는 하나 또는 다른 것을 사용하여 동일한 동작을 달성 할 수 있습니다. 반면에이 차이는 코드의 개념적 이해에 약간의 영향을 미칠 수 있습니다.

패턴을 사용하는 주된 목적은 오히려 개발자간에 공통 언어를 만드는 것입니다. 그래서 중재자를 볼 때 개인적으로 단일 브로커 / 허브를 통해 통신을 시도하는 여러 요소를 이해하여 통신 노이즈를 줄이거 나 (또는 ​​SRP를 촉진하기 위해) 각 개체가 상태 변경 신호를 보내는 능력에 똑같이 중요합니다. 예를 들어, 공항에 접근하는 여러 대의 항공기를 생각해보십시오. 각각은 서로 통신하는 대신 pylon (중개자)을 통해 통신해야합니다. (착륙시 서로 통신하는 1000 대의 항공기를 생각하면 엉망이 될 것입니다)

그러나 관찰자를 보면 내가 신경 쓸 수있는 상태 변경이 있다는 것을 의미하며 특정 상태 변경을 수신하려면 등록 / 구독해야합니다. 상태 변경 신호를 담당하는 중앙 개체가 있습니다. 예를 들어, A에서 B로가는 길에 특정 공항에 관심이 있다면 그 공항에 등록하여 빈 활주로가 있거나 그런 것과 같은 방송되는 이벤트를 볼 수 있습니다.

명확하기를 바랍니다.


5

@cdc는 의도의 차이를 훌륭하게 설명했습니다.

그 위에 정보를 더 추가하겠습니다.

관찰자 : 한 개체의 이벤트 알림을 다른 개체 집합 (다른 클래스의 인스턴스)으로 설정합니다.

중재인 : 특정 클래스에서 생성 된 개체 집합 간의 통신을 중앙 집중화합니다.

dofactory 의 중재자 패턴 구조 :

여기에 이미지 설명 입력

중재자 : 동료간 소통을위한 인터페이스를 정의합니다.

동료 : 동료간에 전달 될 이벤트를 정의하는 추상 클래스입니다.

ConcreteMediator : 동료 개체 를 조정하여 협력 행동을 구현 하고 동료를 유지합니다.

ConcreteColleague : 다른 동료 가 생성 한 Mediator 를 통해 수신 한 알림 작업을 구현합니다.

실제 사례 :

메시 토폴로지 에서 컴퓨터 네트워크를 유지하고 있습니다 . 새 컴퓨터가 추가되거나 기존 컴퓨터가 제거 된 경우 해당 네트워크의 다른 모든 컴퓨터는이 두 이벤트에 대해 알아야합니다.

중재자 패턴이 어떻게 적용되는지 살펴 보겠습니다.

코드 조각 :

import java.util.List;
import java.util.ArrayList;

/* Define the contract for communication between Colleagues. 
   Implementation is left to ConcreteMediator */
interface Mediator{
    public void register(Colleague colleague);
    public void unregister(Colleague colleague);
}
/* Define the contract for notification events from Mediator. 
   Implementation is left to ConcreteColleague
*/
abstract class Colleague{
    private Mediator mediator;
    private String name;

    public Colleague(Mediator mediator,String name){
        this.mediator = mediator;
        this.name = name;
    }
    public String toString(){
        return name;
    }
    public abstract void receiveRegisterNotification(Colleague colleague);
    public abstract void receiveUnRegisterNotification(Colleague colleague);    
}
/*  Process notification event raised by other Colleague through Mediator.   
*/
class ComputerColleague extends Colleague {
    private Mediator mediator;

    public ComputerColleague(Mediator mediator,String name){
        super(mediator,name);
    }
    public  void receiveRegisterNotification(Colleague colleague){
        System.out.println("New Computer register event with name:"+colleague+
        ": received @"+this);
        // Send further messages to this new Colleague from now onwards
    }
    public  void receiveUnRegisterNotification(Colleague colleague){
        System.out.println("Computer left unregister event with name:"+colleague+
        ":received @"+this);
        // Do not send further messages to this Colleague from now onwards
    }
}
/* Act as a central hub for communication between different Colleagues. 
   Notifies all Concrete Colleagues on occurrence of an event
*/
class NetworkMediator implements Mediator{
    List<Colleague> colleagues = new ArrayList<Colleague>();

    public NetworkMediator(){

    }

    public void register(Colleague colleague){
        colleagues.add(colleague);
        for (Colleague other : colleagues){
            if ( other != colleague){
                other.receiveRegisterNotification(colleague);
            }
        }
    }
    public void unregister(Colleague colleague){
        colleagues.remove(colleague);
        for (Colleague other : colleagues){
            other.receiveUnRegisterNotification(colleague);
        }
    }
}

public class MediatorPatternDemo{
    public static void main(String args[]){
        Mediator mediator = new NetworkMediator();
        ComputerColleague colleague1 = new ComputerColleague(mediator,"Eagle");
        ComputerColleague colleague2 = new ComputerColleague(mediator,"Ostrich");
        ComputerColleague colleague3 = new ComputerColleague(mediator,"Penguin");
        mediator.register(colleague1);
        mediator.register(colleague2);
        mediator.register(colleague3);
        mediator.unregister(colleague1);
    }
}

산출:

New Computer register event with name:Ostrich: received @Eagle
New Computer register event with name:Penguin: received @Eagle
New Computer register event with name:Penguin: received @Ostrich
Computer left unregister event with name:Eagle:received @Ostrich
Computer left unregister event with name:Eagle:received @Penguin

설명:

  1. Eagle 은 등록 이벤트를 통해 처음에 네트워크에 추가됩니다. Eagle이 첫 번째이므로 다른 동료에게 알림이 없습니다.
  2. Ostrich 가 네트워크에 추가 되면 Eagle 에게 알림이 전송됩니다. 이제 출력의 1 행이 렌더링됩니다.
  3. Penguin 이 네트워크에 추가 되면 EagleOstrich 가 모두 알림을 받았습니다. 출력의 Line 2 및 Line 3이 이제 렌더링됩니다.
  4. 이글이 등록 해제 이벤트를 통해 네트워크를 떠났을 때 타조펭귄 모두 에게 알림이 전송되었습니다. 출력의 라인 4와 라인 5가 이제 렌더링됩니다.

2

기술적으로 Observer와 Mediator는 모두 동일하며 구성 요소 통신을위한 분리 된 방법을 제공하는 데 사용되지만 사용법은 다릅니다.

구독 된 구성 요소에 상태 변경 (예 : 새 db 레코드 생성)에 대해 obeserver 알리는 동안 명령 구성 요소를 등록 하여 비즈니스 논리 흐름과 관련된 작업을 수행합니다 (암호 재설정을 위해 사용자에게 이메일 보내기).mediator

관찰자

  • 알림 소비자는 알림을 수신하기 위해 구독해야합니다.
  • 알림 처리는 비즈니스 흐름의 일부가 아닙니다.

중재인

  • "게시자"와 "소비자"를 연결하려면 명시적인 등록이 필요합니다.
  • 알림 처리는 특정 비즈니스 흐름의 일부입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.