옵서버 패턴
Observer pattern옵저버 패턴은 서브젝트라는 이름의 객체가 의존자(옵저버)의 목록을 유지하고 상태 변화를 자동으로 통지하는 소프트웨어 설계 패턴입니다.일반적으로 이러한 패턴의 메서드 중 하나를 호출합니다.
주로 "이벤트 구동" 소프트웨어에서 분산형 이벤트 처리 시스템을 구현하기 위해 사용됩니다.이러한 시스템에서, 피사체는 보통 "사건의 흐름" 또는 "사건의 흐름"으로 명명되는 반면, 관찰자는 "사건의 종류"로 불린다.스트림 명명법은 관찰자가 물리적으로 분리되어 있고 대상/스트림 소스에서 방출된 이벤트를 제어할 수 없는 물리적 설정을 암시합니다.이 패턴은, 기동시에 CPU에 사용할 수 없는 입력으로부터 데이터가 착신해, 대신에 「랜덤」(HTTP 요구, GPIO 데이터, 키보드/마우스/분산 데이타베이스로부터의 유저 입력, 블록 체인등)이 되는 모든 프로세스에 적합합니다.대부분의 최신 프로그래밍 언어는 옵저버 패턴 구성요소를 구현하는 내장된 "이벤트" 구조로 구성됩니다.필수는 아니지만 대부분의 '옵저버' 구현에서는 백그라운드스레드를 사용하여 커널에서 제공되는 서브젝트 이벤트 및 기타 지원 메커니즘을 리슨합니다(Linux epoll, ...).
개요
옵저버 설계 패턴은 23개의 잘 알려진 설계 패턴 중 동작 패턴으로 유연하고 재사용 가능한 객체 지향 소프트웨어, 즉 구현, 변경, 테스트 및 [1]재사용이 용이한 객체를 설계하기 위해 반복되는 설계 과제를 해결하는 방법을 설명합니다.
옵저버 설계 패턴이 해결할 수 있는 문제는 무엇입니까?
옵저버 패턴은 다음 [2]문제에 대처합니다.
- 오브젝트를 긴밀하게 결합하지 않고 오브젝트 간의 1대 다 의존성을 정의해야 합니다.
- 하나의 객체가 상태를 변경했을 때 개방된 수의 종속 객체가 자동으로 업데이트되는지 확인해야 합니다.
- 하나의 객체가 다른 객체에 대해 무제한으로 통지할 수 있어야 합니다.
종속 객체의 상태를 직접 업데이트하는 하나의 개체(대상)를 정의하여 개체 간의 일대다 종속성을 정의하는 것은 대상을 특정 종속 객체와 결합하기 때문에 유연하지 않습니다.그러나 성능의 관점에서나 오브젝트 구현이 긴밀하게 결합되어 있는 경우(초당 수천 번 실행되는 낮은 수준의 커널 구조를 생각해 보십시오).긴밀하게 결합된 개체는 다양한 인터페이스를 가진 여러 개체를 참조하고 그에 대해 알고 있기 때문에 일부 시나리오에서는 구현이 어려울 수 있으며 재사용이 어려울 수 있습니다.다른 시나리오에서는 컴파일러가 컴파일 시 오류를 검출하고 CPU 명령 수준에서 코드를 최적화할 수 있기 때문에 긴밀하게 결합된 오브젝트가 더 나은 옵션이 될 수 있습니다.
Observer 설계 패턴은 어떤 솔루션을 설명합니까?
- 정의
Subject
그리고.Observer
물건들. - 따라서 피사체의 상태가 변경되면 등록된 모든 옵서버가 자동으로 통지되고 갱신됩니다(아마도 비동기적으로 갱신될 수 있습니다.
주체의 유일한 책임은 관찰자 목록을 유지하고 관찰자에게 전화하여 상태 변화를 통지하는 것입니다.update()
작동.관찰자의 책임은 관찰자가 피험자에 자신을 등록(및 등록 취소)하고(상태 변경을 통지받도록) 통보받았을 때 상태를 갱신(피험자의 상태와 동기화)하는 것이다.이것은 피험자와 관찰자를 느슨하게 연결시킨다.피험자와 관찰자는 서로에 대한 명확한 지식이 없습니다.관찰자는 런타임에 독립적으로 추가 및 제거할 수 있습니다.이 알림과 등록의 상호작용은 퍼블리시 서브스크라이브라고도 불립니다.
아래 UML 클래스 및 시퀀스 다이어그램을 참조하십시오.
강한 기준과 약한 기준
옵저버 패턴에 의해 메모리누설이 발생할 수 있습니다.이는 리스너 정지 문제로 알려져 있습니다.기본적인 실장에서는 서브젝트가 옵저버에 대한 강한 참조를 유지하고 있기 때문에 폐기 패턴과 같이 명시적 등록과 명시적 등록 해제가 모두 필요하기 때문입니다.이는 관찰자에 대한 언급이 약한 피험자에 의해 예방될 수 있다.
커플링 및 일반적인 pub-sub 구현
일반적으로 옵저버 패턴은 "관측 대상"이 상태 변화가 관찰되는 객체의 일부가 되도록 구현됩니다(옵저버에게 전달됩니다).이러한 유형의 구현은 "긴밀하게 결합"된 것으로 간주되며, 관찰자와 피험자가 서로를 인식하고 내부 부품에 액세스할 수 있도록 합니다.이를 통해 확장성, 속도, 메시지 복구 및 유지보수(이벤트 또는 알림 손실이라고도 함), 조건부 분산의 유연성 결여 및바람직한 보안 조치에 방해가 될 수 있습니다.퍼블리시-서브스크라이브 패턴(일명 pub-sub 패턴)의 일부(비폴링) 구현에서는 옵서버와 관찰 대상 오브젝트 사이의 추가 단계로서 전용의 「메시지 큐」서버(및 경우에 따라서는 여분의 「메시지 핸들러」오브젝트)를 작성해, 컴포넌트를 분리하는 것으로 해결됩니다.이러한 경우 메시지 큐서버는 옵서버 패턴에 의해 액세스 됩니다.옵서버는, 메시지 송신자 자체에 대해서는 아무것도 모르고, 옵서버에 대해서는 아무것도 모르는 채, 「특정 메시지에의 등록」을 실시합니다.이해관계자에 대한 통지 및 통신과 유사한 효과를 얻는 발행-구독 패턴의 다른 구현에서는 옵서버 패턴을 [3][4]전혀 사용하지 않습니다.
OS/2 및 Windows와 같은 멀티 윈도우 운영 체제의 초기 구현에서는 "게시-구독 패턴"과 "이벤트 기반 소프트웨어 개발"이라는 용어가 옵서버 [5]패턴의 동의어로 사용되었습니다.
관찰자 패턴은 GoF 책에서 설명한 바와 같이 매우 기본적인 개념이며 관찰자에게 통지하기 전 또는 후에 관찰된 "주제" 또는 관찰된 "주제"에 의해 수행되어야 하는 특별한 로직에 대한 변경에 대한 관심 제거를 다루지 않는다.또, 변경 통지가 송신되었을 때의 기록이나, 변경 통지가 수신되고 있는 것을 보증하는 것도, 패턴에서는 취급하지 않습니다.이러한 우려는 일반적으로 옵서버 패턴이 극히 일부인 메시지큐잉 시스템에서 처리됩니다.
관련 패턴: 퍼블리시-서브스크라이브 패턴, 미디에이터, 싱글톤.
언커플링
모델 상태가 자주 업데이트되는 경우처럼 게시-구독이 없는 경우 옵서버 패턴을 사용할 수 있습니다.빈번한 업데이트로 인해 뷰가 응답하지 않을 수 있습니다(예를 들어 많은 재도장 콜을 호출하는 등). 이러한 옵서버는 타이머를 사용해야 합니다.따라서 관찰자는 변경 메시지에 의해 오버로드되는 대신 정기적으로 뷰가 모델의 대략적인 상태를 나타내도록 합니다.이 옵서버 모드는 기본 작업의 진행률이 초당 여러 번 변화하는 진행률 막대에 특히 유용합니다.
구조.
UML 클래스 및 시퀀스 다이어그램

위의 UML 클래스 다이어그램에서는Subject
클래스는 종속 객체의 상태를 직접 업데이트하지 않습니다.대신,Subject
를 참조합니다.Observer
인터페이스(update()
)는 상태를 갱신하기 위해,Subject
종속 객체의 상태가 업데이트되는 방식과 무관합니다.그Observer1
그리고.Observer2
클래스가 실장하다Observer
대상 상태와 동기화하여 인터페이스를 만듭니다.
UML 시퀀스 다이어그램은 런타임 상호 작용을 보여 줍니다.그Observer1
그리고.Observer2
오브젝트 호출attach(this)
에Subject1
등록하기 위해서요.의 상태가Subject1
변화들,Subject1
콜notify()
스스로요.
notify()
콜update()
등기부등기하여Observer1
그리고.Observer2
오브젝트:변경된데이터를요구합니다(getState()
)부터Subject1
상태를 갱신(동기화)합니다.
UML 클래스 다이어그램

예
라이브러리 클래스와 기존 라이브러리 클래스는 구현된 모델이 매우 제한적이기 때문에 Java 9에서는 사용되지 않습니다.
키보드 입력을 받아 각 입력 행을 이벤트로 취급하는 Java의 예를 다음에 나타냅니다.문자열이 System.in 에서 제공되는 경우 메서드는notifyObservers
그런 다음 모든 옵서버에게 이벤트 발생을 알리기 위해 '업데이트' 메서드를 호출하는 형식으로 호출됩니다.
자바
수입품 java.displaces를 클릭합니다.목록.; 수입품 java.displaces를 클릭합니다.어레이 리스트; 수입품 java.displaces를 클릭합니다.스캐너; 학급 이벤트 소스 { 일반의 인터페이스 옵서버 { 무효 갱신하다(스트링 이벤트); } 사적인 최종 목록.< >옵서버> 감시자들 = 신규 어레이 리스트<< 고객명 >>님(); 사적인 무효 notify Observers(알림 옵서버)(스트링 이벤트) { 감시자들.각각(옵서버 -> 옵서버.갱신하다(이벤트)); } 일반의 무효 addObserver(옵서버 옵서버) { 감시자들.더하다(옵서버); } 일반의 무효 스캔 시스템인() { 스캐너 스캐너 = 신규 스캐너(시스템..에); 하는 동안에 (스캐너.has Next Line(다음 회선)()) { 스트링 선 = 스캐너.다음 행(); notify Observers(알림 옵서버)(선); } } }
일반의 학급 옵서버 데모 { 일반의 정적인 무효 주된(스트링[] args) { 시스템..나가..인쇄("텍스트 입력:"); 이벤트 소스 이벤트 소스 = 신규 이벤트 소스(); 이벤트 소스.addObserver(이벤트 -> { 시스템..나가..인쇄("답변 수신:" + 이벤트); }); 이벤트 소스.스캔 시스템인(); } }
그루비
학급 이벤트 소스 { 사적인 감시자들 = [] 사적인 notify Observers(알림 옵서버)(스트링 이벤트) { 감시자들.각각 { 그것(이벤트) } } 무효 addObserver(옵서버) { 감시자들 += 옵서버 } 무효 스캔 시스템인() { 변화하다 스캐너 = 신규 스캐너(시스템..에) 하는 동안에 (스캐너) { 변화하다 선 = 스캐너.다음 행() notify Observers(알림 옵서버)(선) } } } 인쇄 '텍스트 입력:' 변화하다 이벤트 소스 = 신규 이벤트 소스() 이벤트 소스.addObserver { 이벤트 -> 인쇄 "수신된 응답: $event" } 이벤트 소스.스캔 시스템인()
코틀린
수입품 java.displaces를 클릭합니다.스캐너 타입 에일리어스 옵서버 = (이벤트: 스트링) -> 구성 단위; 학급 이벤트 소스 { 사적인 변화하다 감시자들 = 가변 리스트 Of< >옵서버>() 사적인 재밌어요 notify Observers(알림 옵서버)(이벤트: 스트링) { 감시자들.각각 { 그것(이벤트) } } 재밌어요 addObserver(옵서버: 옵서버) { 감시자들 += 옵서버 } 재밌어요 스캔 시스템인() { 값 스캐너 = 스캐너(시스템..인) 하는 동안에 (스캐너.다음()) { 값 선 = 스캐너.다음 행() notify Observers(알림 옵서버)(선) } } }
재밌어요 주된(arg: 목록.< >스트링>) { 인쇄("텍스트 입력:") 값 이벤트 소스 = 이벤트 소스() 이벤트 소스.addObserver { 이벤트 -> 인쇄("답변 수신:$이벤트") } 이벤트 소스.스캔 시스템인() }
델파이
사용하다 시스템..범용.컬렉션, 시스템..시스템 유틸; 유형 입출력 서버 = 인터페이스 ['{0C8F4C5D-1898-4F24-91DA-63F1'DD66A692)'] 절차. 갱신하다(컨스턴트 에바루: 스트링); 끝.; 유형 TOB서버 매니저 = 학급 사적인 FOB 서버: 리스트< >입출력 서버>; 일반의 컨스트럭터 만들다; 과부하; 파괴자 파괴하다; 덮어쓰다; 절차. Notify Observers(알림 옵서버)(컨스턴트 에바루: 스트링); 절차. Add Observer(Add Observer)(컨스턴트 AObserver(AObserver(오브서버): 입출력 서버); 절차. Obsrver 등록 취소(컨스턴트 AObserver(AObserver(오브서버): 입출력 서버); 끝.; 유형 리스트너 = 학급(TInterfaced Object(TInterfaced Object), 입출력 서버) 사적인 FName: 스트링; 일반의 컨스트럭터 만들다(컨스턴트 아나메: 스트링); 재도입; 절차. 갱신하다(컨스턴트 에바루: 스트링); 끝.; 절차. TOB서버 매니저.Add Observer(Add Observer)(컨스턴트 AObserver(AObserver(오브서버): 입출력 서버); 시작한다. 한다면 것은 아니다. FOB 서버.포함하다(AObserver(AObserver(오브서버)) 그리고나서 FOB 서버.더하다(AObserver(AObserver(오브서버)); 끝.; 시작한다. 프리앤닐(FOB 서버); 상속된; 끝.; 절차. TOB서버 매니저.Notify Observers(알림 옵서버)(컨스턴트 에바루: 스트링); 변화하다 i: 정수; 시작한다. 위해서 i := 0 로. FOB 서버.세어보세요 - 1 하다 FOB 서버[i].갱신하다(에바루); 끝.; 절차. TOB서버 매니저.Obsrver 등록 취소(컨스턴트 AObserver(AObserver(오브서버): 입출력 서버); 시작한다. 한다면 FOB 서버.포함하다(AObserver(AObserver(오브서버)) 그리고나서 FOB 서버.제거한다.(AObserver(AObserver(오브서버)); 끝.; 컨스트럭터 리스트너.만들다(컨스턴트 아나메: 스트링); 시작한다. 상속된 만들다; FName := 아나메; 끝.; 절차. 리스트너.갱신하다(컨스턴트 에바루: 스트링); 시작한다. 기입(FName + '청취자 알림 수신: ' + 에바루); 끝.; 절차. TMy Form(TMyForm).Observer Example 버튼 클릭(송신자: 토브젝트); 변화하다 LDoor Notify(LDoor 알림): TOB서버 매니저; 리스트너 남편: 입출력 서버; 리스트너 와이프: 입출력 서버; 시작한다. LDoor Notify(LDoor 알림) := TOB서버 매니저.만들다; 해라 리스트너 남편 := 리스트너.만들다('남편'); LDoor Notify(LDoor 알림).Add Observer(Add Observer)(리스트너 남편); 리스트너 와이프 := 리스트너.만들다('아내'); LDoor Notify(LDoor 알림).Add Observer(Add Observer)(리스트너 와이프); LDoor Notify(LDoor 알림).Notify Observers(알림 옵서버)('누군가 문을 두드린다.); 마침내. 프리앤닐(LDoor Notify(LDoor 알림)); 끝.; 끝.;
산출량
남편 청취자가 알림을 받았습니다.누군가가 문을 두드리고 있습니다. 아내 청취자가 알림을 받았습니다.누군가가 문을 두드리고 있어요.
파이썬
Python에서도 비슷한 예가 있습니다.
학급 관찰 가능: 방어하다 __init__(자신): 자신._개요 = [] 방어하다 register_disclossible(자신, 옵서버): 자신._개요.추가하다(옵서버) 방어하다 notify_displays(알림)(자신, *args, **전원): 위해서 관찰하다 에 자신._개요: 관찰하다.알리다(자신, *args, **전원) 학급 옵서버: 방어하다 __init__(자신, 관찰할 수 있다): 관찰할 수 있다.register_disclossible(자신) 방어하다 알리다(자신, 관찰할 수 있다, *args, **전원): 인쇄물("취득", args, 전원, "발신인", 관찰할 수 있다) 주제 = 관찰 가능() 옵서버 = 옵서버(주제) 주제.notify_displays(알림)('테스트", 콰="실패") 인쇄수: <_main_>에서 '테스트', {'kw': '피톤'을 받았습니다.0x0000019757826에 있는 관찰 가능한 개체FD0 >
C#
일반의 학급 페이로드 { 일반의 스트링 메세지 { 얻다; 세트; } }
일반의 학급 주제 : IObservable< >페이로드> { 일반의 리스트< >입출력 서버< >페이로드>> 감시자들 { 얻다; 세트; } 일반의 주제() { 감시자들 = 신규 목록.< >입출력 서버< >페이로드>>( ) ; } 일반의 아이포지터블 구독(입출력 서버< >페이로드> 옵서버) { 한다면 (!감시자들.포함하다(옵서버)) { 감시자들.더하다(옵서버); } 돌아가다 신규 서브스크라이버(옵서버, 감시자들); } 일반의 무효 Send Message(메시지 보내기)(스트링 메세지) { 앞지르다 (변화하다 옵서버 에 감시자들) { 옵서버.온넥스트(신규 페이로드 { 메세지 = 메세지 }); } } }
일반의 학급 서브스크라이버 : 아이포지터블 { 사적인 입출력 서버< >페이로드> 옵서버; 사적인 리스트< >입출력 서버< >페이로드>> 감시자들; 일반의 서브스크라이버( 입출력 서버< >페이로드> 옵서버, 리스트< >입출력 서버< >페이로드>> 감시자들) { 이것..옵서버 = 옵서버; 이것..감시자들 = 감시자들; } 일반의 무효 폐기하다() { 한다면 (옵서버 != 무효 & & 감시자들.포함하다(옵서버)) { 감시자들.제거한다.(옵서버); } } }
일반의 학급 옵서버 : 입출력 서버< >페이로드> { 일반의 스트링 메세지 { 얻다; 세트; } 일반의 무효 On Completed(OnCompleted)() { } 일반의 무효 On Error(OnError)(예외. 에러) { } 일반의 무효 온넥스트(페이로드 가치) { 메세지 = 가치.메세지; } 일반의 아이포지터블 등록하세요(주제 주제) { 돌아가다 주제.구독(이것.); } }
자바스크립트
JavaScript는 사용되지 않습니다.Object.observe
Observer [7]패턴을 보다 정확하게 구현한 함수입니다.이렇게 하면 관찰된 물체를 변경할 때 이벤트가 발생합니다.권장되지 않음Object.observe
함수, 프로그래머는 여전히 보다 명시적인 [8]코드로 패턴을 구현할 수 있습니다.
허락하다 주제 = { _스테이트: 0, _개요: [], 더하다: 기능.(옵서버) { 이것.._개요.밀다(옵서버); }, 상태 가져오기: 기능.() { 돌아가다 이것.._스테이트; }, setState(설정 상태): 기능.(가치) { 이것.._스테이트 = 가치; 위해서 (허락하다 i = 0; i < > 이것.._개요.길이; i++) { 이것.._개요[i].신호.(이것.); } } }; 허락하다 옵서버 = { 신호.: 기능.(주제) { 허락하다 현재의 가치 = 주제.상태 가져오기(); 콘솔.로그.(현재의 가치); } } 주제.더하다(옵서버); 주제.setState(설정 상태)(10); //console.log 출력 - 10
「 」를 참조해 주세요.
- 암묵적인 호출
- 클라이언트 서버 모델
- 옵서버 패턴은 엔티티-컴포넌트-시스템 패턴에서 자주 사용됩니다.
레퍼런스
- ^ Erich Gamma; Richard Helm; Ralph Johnson; John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 293ff. ISBN 0-201-63361-2.
- ^ "The Observer design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
- ^ 다양한 관찰자 패턴 구현 간의 비교 Moshe Bindler, 2015(Github)
- ^ 펍/서브와 옵저버 패턴의 차이 Adi Osmani의 옵저버 패턴 (Safari books on-line)
- ^ Windows 프로그래밍 경험 Charles Petzold, 1992년 11월 10일, PC Magazine (Google Books)
- ^ "The Observer design pattern - Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
- ^ "jQuery - Listening for variable changes in JavaScript".
- ^ "Jquery - Listening for variable changes in JavaScript".