观察者
范围
对象
目的
行为
意图
定义对象之间的一对多依赖关系,以便当一个对象改变状态(主题)时,所有依赖于它的对象(观察者)都会自动收到通知并更新。
适用性
- 当一个抽象有两个方面,其中一个依赖于另一个时
- 当一个对象的更改需要更改其他对象,而你不知道需要更改多少个对象时
- 当一个对象应该通知其他对象,而无需假设这些对象是谁时
结构
后果
- + 模块化:主题和观察者可以独立变化
- + 可扩展性:可以定义和添加任意数量的观察者
- + 可定制性:不同的观察者提供主题的不同视图
- - 意外更新:观察者彼此之间并不知道
- - 更新开销:可能需要提示
实现
- 主题-观察者映射
- 悬空引用
- 避免观察者特定的更新协议:推和拉模型
- 显式注册感兴趣的修改
相关模式
描述
- 问题
- 在应用程序中的一个或多个地方,我们需要了解系统事件或应用程序状态更改。我们希望有一种标准的方法来订阅监听系统事件,以及一种标准的方法来通知感兴趣的方。在感兴趣的方订阅系统事件或应用程序状态更改后,通知应该是自动的。还应该有一种取消订阅的方法。
- 力量
- 观察者和可观察者可能应该由对象表示。观察者对象将由可观察者对象通知。
- 解决方案
- 订阅后,监听对象将通过方法调用方式收到通知。
- 松散耦合
- 当两个对象松散耦合时,它们可以交互,但它们彼此了解得很少。努力在交互的对象之间实现松散耦合的设计。
- 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