觀察者
範圍
物件
目的
行為
意圖
定義物件之間的一對多依賴關係,以便當一個物件改變狀態(主題)時,所有依賴於它的物件(觀察者)都會自動收到通知並更新。
適用性
- 當一個抽象有兩個方面,其中一個依賴於另一個時
- 當一個物件的更改需要更改其他物件,而你不知道需要更改多少個物件時
- 當一個物件應該通知其他物件,而無需假設這些物件是誰時
結構

後果
- + 模組化:主題和觀察者可以獨立變化
- + 可擴充套件性:可以定義和新增任意數量的觀察者
- + 可定製性:不同的觀察者提供主題的不同檢視
- - 意外更新:觀察者彼此之間並不知道
- - 更新開銷:可能需要提示
實現
- 主題-觀察者對映
- 懸空引用
- 避免觀察者特定的更新協議:推和拉模型
- 顯式註冊感興趣的修改
相關模式
描述
- 問題
- 在應用程式中的一個或多個地方,我們需要了解系統事件或應用程式狀態更改。我們希望有一種標準的方法來訂閱監聽系統事件,以及一種標準的方法來通知感興趣的方。在感興趣的方訂閱系統事件或應用程式狀態更改後,通知應該是自動的。還應該有一種取消訂閱的方法。
- 力量
- 觀察者和可觀察者可能應該由物件表示。觀察者物件將由可觀察者物件通知。
- 解決方案
- 訂閱後,監聽物件將透過方法呼叫方式收到通知。
- 鬆散耦合
- 當兩個物件鬆散耦合時,它們可以互動,但它們彼此瞭解得很少。努力在互動的物件之間實現鬆散耦合的設計。
- Subject 唯一知道的關於觀察者的事情是它實現了某個介面
- 我們可以隨時新增新的觀察者
- 我們永遠不需要修改主題來新增新的觀察者型別
- 我們可以獨立地重複使用主題或觀察者
- 主題或觀察者的更改不會影響另一個
例子
觀察者模式在 Java 中被廣泛使用。例如,在以下程式碼段中
button是主題MyListener是觀察者actionPerformed()等價於update()
JButton button = new JButton("Click me!");
button.addActionListener(new MyListener());
class MyListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
...
}
}
另一個例子是 PropertyChangeSupport。Component 類使用 PropertyChangeSupport 物件讓感興趣的觀察者註冊以接收有關標籤、面板和其他 GUI 元件屬性更改的通知。

你能在上面的類圖中找到 Subject、Observer 和 update() 嗎?
Component是主題PropertyChangeListener是觀察者propertyChange()等價於update()
成本
如果你不注意,這種模式可能很棘手。注意,表示主題狀態更改的資料可能會在不更改介面的情況下演變。如果你只傳輸一個字串,如果至少有一個新的觀察者也需要一個狀態碼,則模式可能會變得非常昂貴。除非你知道主題狀態的實現永遠不會改變,否則你應該使用中介者。
建立
這種模式的建立成本很高。
維護
這種模式的維護成本可能很高。
刪除
這種模式的刪除成本也很高。
建議
- 在主題和觀察者類的名稱中新增 subject 和 observer 術語,以向其他開發人員表明該模式的使用。
- 為了提高效能,你可以只向觀察者傳送狀態差異,而不是新狀態。觀察者只能根據主題改變的部分進行更新,而不是主題的所有狀態。
實施
// Main Class
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
private var _cs:ConcreteSubject = new ConcreteSubject();
private var _co1:ConcreteObserver1 = new ConcreteObserver1();
private var _co2:ConcreteObserver2 = new ConcreteObserver2();
public function Main() {
_cs.registerObserver(_co1);
_cs.registerObserver(_co2);
_cs.changeState(10);
_cs.changeState(99);
_cs.unRegisterObserver(_co1);
_cs.changeState(17);
_co1 = null;
}
}
}
// Interface Subject
package {
public interface ISubject {
function registerObserver(o:IObserver):void;
function unRegisterObserver(o:IObserver):void;
function updateObservers():void;
function changeState(newState:uint):void;
}
}
// Interface Observer
package {
public interface IObserver {
function update(newState:uint):void;
}
}
// Concrete Subject
package {
public class ConcreteSubject implements ISubject {
private var _observersList:Array = new Array();
private var _currentState:uint;
public function ConcreteSubject() {
}
public function registerObserver(o:IObserver):void {
_observersList.push( o );
_observersList[_observersList.length-1].update(_currentState); // update newly registered
}
public function unRegisterObserver(o:IObserver):void {
_observersList.splice( _observersList.indexOf( o ), 1 );
}
public function updateObservers():void {
for( var i:uint = 0; i<_observersList.length; i++) {
_observersList[i].update(_currentState);
}
}
public function changeState(newState:uint):void {
_currentState = newState;
updateObservers();
}
}
}
// Concrete Observer 1
package {
public class ConcreteObserver1 implements IObserver {
public function ConcreteObserver1() {
}
public function update(newState:uint):void {
trace( "co1: "+newState );
}
// other Observer specific methods
}
}
// Concrete Observer 2
package {
public class ConcreteObserver2 implements IObserver {
public function ConcreteObserver2() {
}
public function update(newState:uint):void {
trace( "co2: "+newState );
}
// other Observer specific methods
}
}
傳統方法
C# 和其他 .NET Framework 語言通常不需要使用介面和具體物件來完全實現觀察者模式。以下是如何使用它們的示例,但是。
using System;
using System.Collections;
namespace Wikipedia.Patterns.Observer
{
// IObserver --> interface for the observer
public interface IObserver
{
// called by the subject to update the observer of any change
// The method parameters can be modified to fit certain criteria
void Update(string message);
}
public class Subject
{
// use array list implementation for collection of observers
private ArrayList observers;
// constructor
public Subject()
{
observers = new ArrayList();
}
public void Register(IObserver observer)
{
// if list does not contain observer, add
if (!observers.Contains(observer))
{
observers.Add(observer);
}
}
public void Unregister(IObserver observer)
{
// if observer is in the list, remove
observers.Remove(observer);
}
public void Notify(string message)
{
// call update method for every observer
foreach (IObserver observer in observers)
{
observer.Update(message);
}
}
}
// Observer1 --> Implements the IObserver
public class Observer1 : IObserver
{
public void Update(string message)
{
Console.WriteLine("Observer1:" + message);
}
}
// Observer2 --> Implements the IObserver
public class Observer2 : IObserver
{
public void Update(string message)
{
Console.WriteLine("Observer2:" + message);
}
}
// Test class
public class ObserverTester
{
[STAThread]
public static void Main()
{
Subject mySubject = new Subject();
IObserver myObserver1 = new Observer1();
IObserver myObserver2 = new Observer2();
// register observers
mySubject.Register(myObserver1);
mySubject.Register(myObserver2);
mySubject.Notify("message 1");
mySubject.Notify("message 2");
}
}
}
使用事件
在 C# 和其他 .NET Framework 語言(如 Visual Basic)中,使用具體和抽象觀察者和釋出者的替代方法是使用事件。事件模型透過 委託 來支援,委託定義了用於捕獲事件的方法簽名。因此,委託提供了由抽象觀察者提供的調解,方法本身提供了具體觀察者,具體主題是定義事件的類,主題是內置於基類庫中的事件系統。它是.NET 應用程式中實現觀察者模式的首選方法。
using System;
// First, declare a delegate type that will be used to fire events.
// This is the same delegate as System.EventHandler.
// This delegate serves as the abstract observer.
// It does not provide the implementation, but merely the contract.
public delegate void EventHandler(object sender, EventArgs e);
// Next, declare a published event. This serves as the concrete subject.
// Note that the abstract subject is handled implicitly by the runtime.
public class Button
{
// The EventHandler contract is part of the event declaration.
public event EventHandler Clicked;
// By convention,.NET events are fired from descendant classes by a virtual method,
// allowing descendant classes to handle the event invocation without subscribing
// to the event itself.
protected virtual void OnClicked(EventArgs e)
{
if (Clicked != null)
Clicked(this, e); // implicitly calls all observers/subscribers
}
}
// Then in an observing class, you are able to attach and detach from the events:
public class Window
{
private Button okButton;
public Window()
{
okButton = new Button();
// This is an attach function. Detaching is accomplished with -=.
// Note that it is invalid to use the assignment operator - events are multicast
// and can have multiple observers.
okButton.Clicked += new EventHandler(okButton_Clicked);
}
private void okButton_Clicked(object sender, EventArgs e)
{
// This method is called when Clicked(this, e) is called within the Button class
// unless it has been detached.
}
}
便捷的實現
你可以在 Java 中像這樣實現這種模式
// Observer pattern -- Structural example
// @since JDK 5.0
import java.util.ArrayList;
// "Subject"
abstract class Subject {
// Fields
private ArrayList<Observer> observers = new ArrayList<Observer>();
// Methods
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer o : observers)
o.update();
}
}
// "ConcreteSubject"
class ConcreteSubject extends Subject {
// Fields
private String subjectState;
// Properties
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String value) {
subjectState = value;
}
}
// "Observer"
abstract class Observer {
// Methods
abstract public void update();
}
// "ConcreteObserver"
class ConcreteObserver extends Observer {
// Fields
private String name;
private String observerState;
private ConcreteSubject subject;
// Constructors
public ConcreteObserver(ConcreteSubject subject, String name) {
this.subject = subject;
this.name = name;
//subject.attach(this);
}
// Methods
public void update() {
observerState = subject.getSubjectState();
System.out.printf("Observer %s's new state is %s\n", name, observerState);
}
}
// Client test
public class Client {
public static void main(String[] args) {
// Configure Observer structure
ConcreteSubject s = new ConcreteSubject();
s.attach(new ConcreteObserver(s, "A"));
s.attach(new ConcreteObserver(s, "B"));
s.attach(new ConcreteObserver(s, "C"));
// Change subject and notify observers
s.setSubjectState("NEW");
s.notifyObservers();
}
}
內建支援
Java JDK 有這種模式的幾個實現:在圖形使用者介面中的應用程式,例如在 AWT 工具包、Swing 等中。在 Swing 中,每當使用者單擊按鈕或調整滑塊時,應用程式中的許多物件可能需要對更改做出反應。Swing 將感興趣的客戶端(觀察者)稱為“監聽器”,並允許你註冊任意數量的監聽器以接收有關元件事件的通知。
MVC 在 Swing 中更像 M(VC),即檢視和控制器緊密耦合;Swing 沒有將檢視與控制器分開。MVC 支援 n 層開發,即鬆散耦合的層(見下文),這些層可以獨立更改,甚至可以在不同的機器上執行。
也內建了對觀察者模式的支援。你所要做的就是擴充套件 java.util.Observable(主題)並告訴它何時通知 java.util.Observer s。API 會為你完成剩下的工作。你可以使用推或拉樣式來更新你的觀察者。

java.util.Observable 是一個類,而 java.util.Observer 是一個介面。
public void setValue(double value) {
this.value = value;
setChanged();
notifyObservers();
}
請注意,你必須呼叫 setChanged(),這樣 Observable 程式碼才能廣播更改。notifyObservers() 方法呼叫每個註冊觀察者的 update() 方法。update() 方法是 Observer 介面實現者的要求。
// Observer pattern -- Structural example
import java.util.Observable;
import java.util.Observer;
// "Subject"
class ConcreteSubject extends Observable {
// Fields
private String subjectState;
// Methods
public void dataChanged() {
setChanged();
notifyObservers(); // use the pull method
}
// Properties
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String value) {
subjectState = value;
dataChanged();
}
}
// "ConcreteObserver"
import java.util.Observable;
import java.util.Observer;
class ConcreteObserver implements Observer {
// Fields
private String name;
private String observerState;
private Observable subject;
// Constructors
public ConcreteObserver(Observable subject, String name) {
this.subject = subject;
this.name = name;
subject.addObserver(this);
}
// Methods
public void update(Observable subject, Object arg) {
if (subject instanceof ConcreteSubject) {
ConcreteSubject subj = (ConcreteSubject)subject;
observerState = subj.getSubjectState();
System.out.printf("Observer %s's new state is %s\n", name, observerState);
}
}
}
// Client test
public class Client {
public static void main(String[] args) {
// Configure Observer structure
ConcreteSubject s = new ConcreteSubject();
new ConcreteObserver(s, "A");
new ConcreteObserver(s, "B");
new ConcreteObserver(s, "C");
// Change subject and notify observers
s.setSubjectState("NEW");
}
}
鍵盤處理
以下是使用 Java 編寫的示例,它接收鍵盤輸入並將每行輸入視為事件。該示例基於庫類 java.util.Observer 和 java.util.Observable。當從 System.in 提供字串時,呼叫方法 notifyObservers,以便以呼叫其 'update' 方法的形式通知所有觀察者事件的發生——在本例中,ResponseHandler.update(...)。
檔案 MyApp.java 包含一個 main() 方法,該方法可用於執行程式碼。
/* Filename : EventSource.java */
package org.wikibooks.obs;
import java.util.Observable; // Observable is here
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class EventSource extends Observable implements Runnable {
@Override
public void run() {
try {
final InputStreamReader isr = new InputStreamReader(System.in);
final BufferedReader br = new BufferedReader(isr);
while (true) {
String response = br.readLine();
setChanged();
notifyObservers(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* Filename : ResponseHandler.java */
package org.wikibooks.obs;
import java.util.Observable;
import java.util.Observer; /* this is Event Handler */
public class ResponseHandler implements Observer {
private String resp;
public void update(Observable obj, Object arg) {
if (arg instanceof String) {
resp = (String) arg;
System.out.println("\nReceived Response: " + resp );
}
}
}
/* Filename : MyApp.java */
/* This is the main program */
package org.wikibooks.obs;
public class MyApp {
public static void main(String[] args) {
System.out.println("Enter Text >");
// create an event source - reads from stdin
final EventSource eventSource = new EventSource();
// create an observer
final ResponseHandler responseHandler = new ResponseHandler();
// subscribe the observer to the event source
eventSource.addObserver(responseHandler);
// starts the event thread
Thread thread = new Thread(eventSource);
thread.start();
}
}
觀察者模式的 Java 實現有優缺點
優點
- 它隱藏了觀察者模式的許多細節
- 它可以以推和拉方式使用。
缺點
- 因為
Observable是一個類,所以你必須對其進行子類化;你不能將Observable行為新增到子類化其他超類的現有類中(違反了面向介面程式設計原則)。如果你不能對Observable進行子類化,那麼使用委託,即為你的類提供一個 Observable 物件,並讓你的類轉發關鍵方法呼叫給它。 - 因為
setChanged()是受保護的,所以你不能偏愛組合而不是繼承。
class STUDENT
<?php
class Student implements SplObserver {
protected $type = "Student";
private $name;
private $address;
private $telephone;
private $email;
private $_classes = array();
public function __construct($name)
{
$this->name = $name;
}
public function GET_type()
{
return $this->type;
}
public function GET_name()
{
return $this->name;
}
public function GET_email()
{
return $this->email;
}
public function GET_telephone()
{
return $this->telephone;
}
public function update(SplSubject $object)
{
$object->SET_log("Comes from ".$this->name.": I'm a student of ".$object->GET_materia());
}
}
?>
class TEACHER
<?php
class Teacher implements SplObserver {
protected $type = "Teacher";
private $name;
private $address;
private $telephone;
private $email;
private $_classes = array();
public function __construct($name)
{
$this->name = $name;
}
public function GET_type()
{
return $this->type;
}
public function GET_name()
{
return $this->name;
}
public function GET_email()
{
return $this->email;
}
public function GET_telephone()
{
return $this->name;
}
public function update(SplSubject $object)
{
$object->SET_log("Comes from ".$this->name.": I teach in ".$object->GET_materia());
}
}
?>
Class SUBJECT
<?php
class Subject implements SplSubject {
private $name_materia;
private $_observers = array();
private $_log = array();
function __construct($name)
{
$this->name_materia = $name;
$this->_log[] = "Subject $name was included";
}
/* Add an observer */
public function attach(SplObserver $classes) {
$this->_classes[] = $classes;
$this->_log[] = " The ".$classes->GET_type()." ".$classes->GET_name()." was included";
}
/* Remove an observer */
public function detach(SplObserver $classes) {
foreach ($this->_classes as $key => $obj) {
if ($obj == $classes) {
unset($this->_classes[$key]);
$this->_log[] = " The ".$classes->GET_type()." ".$classes->GET_name()." was removed";
}
}
}
/* Notificate an observer */
public function notify(){
foreach ($this->_classes as $classes){
$classes->update($this);
}
}
public function GET_materia()
{
return $this->name_materia;
}
function SET_log($valor)
{
$this->_log[] = $valor ;
}
function GET_log()
{
return $this->_log;
}
}
?>
應用
<?php
require_once("teacher.class.php");
require_once("student.class.php");
require_once("subject.class.php");
$subject = new Subject("Math");
$marcus = new Teacher("Marcus Brasizza");
$rafael = new Student("Rafael");
$vinicius = new Student("Vinicius");
// Include observers in the math Subject
$subject->attach($rafael);
$subject->attach($vinicius);
$subject->attach($marcus);
$subject2 = new Subject("English");
$renato = new Teacher("Renato");
$fabio = new Student("Fabio");
$tiago = new Student("Tiago");
// Include observers in the english Subject
$subject2->attach($renato);
$subject2->attach($vinicius);
$subject2->attach($fabio);
$subject2->attach($tiago);
// Remove the instance "Rafael from subject"
$subject->detach($rafael);
// Notify both subjects
$subject->notify();
$subject2->notify();
echo "First Subject <br>";
echo "<pre>";
print_r($subject->GET_log());
echo "</pre>";
echo "<hr>";
echo "Second Subject <br>";
echo "<pre>";
print_r($subject2->GET_log());
echo "</pre>";
?>
輸出
第一門科目
Array
(
[0] => Subject Math was included
[1] => The Student Rafael was included
[2] => The Student Vinicius was included
[3] => The Teacher Marcus Brasizza was included
[4] => The Student Rafael was removed
[5] => Comes from Vinicius: I'm a student of Math
[6] => Comes from Marcus Brasizza: I teach in Math
)
第二門科目
Array
(
[0] => Subject English was included
[1] => The Teacher Renato was included
[2] => The Student Vinicius was included
[3] => The Student Fabio was included
[4] => The Student Tiago was included
[5] => Comes from Renato: I teach in English
[6] => Comes from Vinicius: I'm a student of English
[7] => Comes from Fabio: I'm a student of English
[8] => Comes from Tiago: I'm a student of English
)
在 Python 中的觀察者模式
class AbstractSubject:
def register(self, listener):
raise NotImplementedError("Must subclass me")
def unregister(self, listener):
raise NotImplementedError("Must subclass me")
def notify_listeners(self, event):
raise NotImplementedError("Must subclass me")
class Listener:
def __init__(self, name, subject):
self.name = name
subject.register(self)
def notify(self, event):
print self.name, "received event", event
class Subject(AbstractSubject):
def __init__(self):
self.listeners = []
self.data = None
def getUserAction(self):
self.data = raw_input('Enter something to do:')
return self.data
# Implement abstract Class AbstractSubject
def register(self, listener):
self.listeners.append(listener)
def unregister(self, listener):
self.listeners.remove(listener)
def notify_listeners(self, event):
for listener in self.listeners:
listener.notify(event)
if __name__=="__main__":
# make a subject object to spy on
subject = Subject()
# register two listeners to monitor it.
listenerA = Listener("<listener A>", subject)
listenerB = Listener("<listener B>", subject)
# simulated event
subject.notify_listeners ("<event 1>")
# outputs:
# <listener A> received event <event 1>
# <listener B> received event <event 1>
action = subject.getUserAction()
subject.notify_listeners(action)
#Enter something to do:hello
# outputs:
# <listener A> received event hello
# <listener B> received event hello
使用 函式裝飾器,可以在 Python 中更簡潔地實現觀察者模式。
在 Ruby 中,使用標準的 Observable 混合。有關文件和示例,請參閱 http://www.ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html
