跳转到内容

观察者

25% developed
来自维基教科书,开放的书籍,为一个开放的世界

模型-视图-控制器 计算机科学设计模式
观察者
原型

范围

对象

目的

行为

意图

定义对象之间的一对多依赖关系,以便当一个对象改变状态(主题)时,所有依赖于它的对象(观察者)都会自动收到通知并更新。

适用性

  • 当一个抽象有两个方面,其中一个依赖于另一个时
  • 当一个对象的更改需要更改其他对象,而你不知道需要更改多少个对象时
  • 当一个对象应该通知其他对象,而无需假设这些对象是谁时

结构

后果

  • + 模块化:主题和观察者可以独立变化
  • + 可扩展性:可以定义和添加任意数量的观察者
  • + 可定制性:不同的观察者提供主题的不同视图
  • - 意外更新:观察者彼此之间并不知道
  • - 更新开销:可能需要提示

实现

  • 主题-观察者映射
  • 悬空引用
  • 避免观察者特定的更新协议:推和拉模型
  • 显式注册感兴趣的修改
  • 单例,用于使可观察对象唯一并全局可访问。
  • 中介者,用于封装更新对象

描述

问题
在应用程序中的一个或多个地方,我们需要了解系统事件或应用程序状态更改。我们希望有一种标准的方法来订阅监听系统事件,以及一种标准的方法来通知感兴趣的方。在感兴趣的方订阅系统事件或应用程序状态更改后,通知应该是自动的。还应该有一种取消订阅的方法。
力量
观察者和可观察者可能应该由对象表示。观察者对象将由可观察者对象通知。
解决方案
订阅后,监听对象将通过方法调用方式收到通知。
松散耦合
当两个对象松散耦合时,它们可以交互,但它们彼此了解得很少。努力在交互的对象之间实现松散耦合的设计。
  • 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) {
      ...
    }  
 }

另一个例子是 PropertyChangeSupportComponent 类使用 PropertyChangeSupport 对象让感兴趣的观察者注册以接收有关标签、面板和其他 GUI 组件属性更改的通知。

你能在上面的类图中找到 SubjectObserverupdate() 吗?

  • Component 是主题
  • PropertyChangeListener 是观察者
  • propertyChange() 等价于 update()

成本

如果你不注意,这种模式可能很棘手。注意,表示主题状态更改的数据可能会在不更改接口的情况下演变。如果你只传输一个字符串,如果至少有一个新的观察者也需要一个状态码,则模式可能会变得非常昂贵。除非你知道主题状态的实现永远不会改变,否则你应该使用中介者。

创建

这种模式的创建成本很高。

维护

这种模式的维护成本可能很高。

删除

这种模式的删除成本也很高。

建议

  • 在主题和观察者类的名称中添加 subjectobserver 术语,以向其他开发人员表明该模式的使用。
  • 为了提高性能,你可以只向观察者发送状态差异,而不是新状态。观察者只能根据主题改变的部分进行更新,而不是主题的所有状态。

实施

在 ActionScript 3 中的实现
// 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# 中的实现

传统方法

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 中的实现

便捷的实现

你可以在 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.Observerjava.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() 是受保护的,所以你不能偏爱组合而不是继承。
在 PHP 中的实现

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 中的实现

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 中的实现

在 Ruby 中,使用标准的 Observable 混合。有关文档和示例,请参阅 http://www.ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html


Clipboard

待办事项
添加更多插图。


模型-视图-控制器 计算机科学设计模式
观察者
原型


你对本页有疑问吗?
在这里提问


创建本教材中的新页面


华夏公益教科书