观察者模式
觀察者模式是軟體設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實时事件處理系統。
結構
參與類別
參與本模式的各類別列出如下。成員函式以模擬的方式列出。
抽象目標類別
此抽象類別提供一個界面讓觀察者進行添附與解附作業。此類別內有個不公開的觀察者串鍊,並透過下列函式(方法)進行作業
- 添附(Attach):新增觀察者到串鍊內,以追蹤目標物件的變化。
- 解附(Detach):將已經存在的觀察者從串鍊中移除。
- 通知(Notify):利用觀察者所提供的更新函式來通知此目標已經產生變化。
添附函式包涵了一個觀察者物件參數。也許是觀察者類別的虛擬函式(即更新函式),或是在非物件導向的設定中所使用的函式指標(更廣泛來講,函式子或是函式物件)。
目標類別
此類別提供了觀察者欲追蹤的狀態。也利用其源類別(例如前述的抽象目標類別)所提供的方法,來通知所有的觀察者其狀態已經更新。此類別擁有以下函式
- 取得狀態(GetState):回傳該目標物件的狀態。
抽象觀察者介面
抽象觀察者類別是一個必須被實做的抽象類別。這個類別定義了所有觀察者都擁有的更新用介面,此介面是用來接收目標類別所發出的更新通知。此類別含有以下函式
- 更新(Update):會被實做的一個抽象(虛擬)函式。
觀察者類別
這個類別含有指向目標類別的參考(reference),以接收來自目標類別的更新狀態。此類別含有以下函式
- 更新(Update):是前述抽象函式的實做。當這個函式被目標物件呼叫時,觀察者物件將會呼叫目標物件的取得狀態函式,來其所擁有的更新目標物件資訊。
每個觀察者類別都要實做它自己的更新函式,以應對狀態更新的情形。
當目標物件改變時,會通過呼叫它自己的通知函式來將通知送給每一個觀察者物件,這個通知函式則會去呼叫已經添附在串鍊內的觀察者更新函式。通知與更新函式可能會有一些參數,好指明是目前目標物件內的何種改變。這麼作將可增進觀察者的效率(只更新那些改變部份的狀態)。
用途
- 當抽象個體有兩個互相依賴的層面時。封裝這些層面在單獨的物件內將可允許程式設計師單獨地去變更與重複使用這些物件,而不會產生兩者之間交互的問題。
- 當其中一個物件的變更會影響其他物件,卻又不知道多少物件必須被同時變更時。
- 當物件應該有能力通知其他物件,又不應該知道其他物件的實做細節時。
觀察者模式通常與 MVC 範式有關係。在 MVC 中,觀察者模式被用來降低 model 與 view 的耦合程度。一般而言, model 的改變會觸發通知其他身為觀察者的 model 。而這些 model 實際上是 view 。 Java Swing 就是個範例,示意了 model 預期會透過 PropertyChangeNotification 架構以送出改變的通知給其他 view 。 Model 類別是 Java bean 類別的一員,並擁有與上述目標類別同樣的行為。 View 類別則繫結了一些 GUI 中的可視元素,並擁有與上述觀察者類別同樣的行為。當應用程式在執行時。使用者將因 view 做出相應的更新而看見 model 所產生的變更。
示例
Python
class AbstractSubject(object):
def register(self, listener):
raise NotImplementedError("Must subclass me")
def deregister(self, listener):
raise NotImplementedError("Must subclass me")
def notify_listeners(self, event):
raise NotImplementedError("Must subclass me")
class Listener(object):
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 deregister(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
C#
C#和其他使用.NET Framework的語言一般無需使用接口和類實現典型的觀察者模式,但是這裡依然給一個例子。
using System;
using System.Collections;
namespace Wikipedia.Patterns.Strategy
{
// 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 Deregister(IObserver observer)
{
// if observer is in the list, remove
if (observers.Contains(observer))
{
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++
#include <list>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
// The Abstract Observer
class ObserverBoardInterface
{
public:
virtual void update(float a,float b,float c) = 0;
};
// Abstract Interface for Displays
class DisplayBoardInterface
{
public:
virtual void show() = 0;
};
// The Abstract Subject
class WeatherDataInterface
{
public:
virtual void registerob(ObserverBoardInterface* ob) = 0;
virtual void removeob(ObserverBoardInterface* ob) = 0;
virtual void notifyOb() = 0;
};
// The Concrete Subject
class ParaWeatherData: public WeatherDataInterface
{
public:
void SensorDataChange(float a,float b,float c)
{
m_humidity = a;
m_temperature = b;
m_pressure = c;
notifyOb();
}
void registerob(ObserverBoardInterface* ob)
{
m_obs.push_back(ob);
}
void removeob(ObserverBoardInterface* ob)
{
m_obs.remove(ob);
}
protected:
void notifyOb()
{
list<ObserverBoardInterface*>::iterator pos = m_obs.begin();
while (pos != m_obs.end())
{
((ObserverBoardInterface* )(*pos))->update(m_humidity,m_temperature,m_pressure);
(dynamic_cast<DisplayBoardInterface*>(*pos))->show();
++pos;
}
}
private:
float m_humidity;
float m_temperature;
float m_pressure;
list<ObserverBoardInterface* > m_obs;
};
// A Concrete Observer
class CurrentConditionBoard : public ObserverBoardInterface, public DisplayBoardInterface
{
public:
CurrentConditionBoard(WeatherDataInterface& a):m_data(a)
{
m_data.registerob(this);
}
void show()
{
cout<<"_____CurrentConditionBoard_____"<<endl;
cout<<"humidity: "<<m_h<<endl;
cout<<"temperature: "<<m_t<<endl;
cout<<"pressure: "<<m_p<<endl;
cout<<"_______________________________"<<endl;
}
void update(float h, float t, float p)
{
m_h = h;
m_t = t;
m_p = p;
}
private:
float m_h;
float m_t;
float m_p;
WeatherDataInterface& m_data;
};
// A Concrete Observer
class StatisticBoard : public ObserverBoardInterface, public DisplayBoardInterface
{
public:
StatisticBoard(WeatherDataInterface& a):m_maxt(-1000),m_mint(1000),m_avet(0),m_count(0),m_data(a)
{
m_data.registerob(this);
}
void show()
{
cout<<"________StatisticBoard_________"<<endl;
cout<<"lowest temperature: "<<m_mint<<endl;
cout<<"highest temperature: "<<m_maxt<<endl;
cout<<"average temperature: "<<m_avet<<endl;
cout<<"_______________________________"<<endl;
}
void update(float h, float t, float p)
{
++m_count;
if (t>m_maxt)
{
m_maxt = t;
}
if (t<m_mint)
{
m_mint = t;
}
m_avet = (m_avet * (m_count-1) + t)/m_count;
}
private:
float m_maxt;
float m_mint;
float m_avet;
int m_count;
WeatherDataInterface& m_data;
};
int main(int argc, char *argv[])
{
ParaWeatherData * wdata = new ParaWeatherData;
CurrentConditionBoard* currentB = new CurrentConditionBoard(*wdata);
StatisticBoard* statisticB = new StatisticBoard(*wdata);
wdata->SensorDataChange(10.2, 28.2, 1001);
wdata->SensorDataChange(12, 30.12, 1003);
wdata->SensorDataChange(10.2, 26, 806);
wdata->SensorDataChange(10.3, 35.9, 900);
wdata->removeob(currentB);
wdata->SensorDataChange(100, 40, 1900);
delete statisticB;
delete currentB;
delete wdata;
return 0;
}
PHP
class STUDENT
<?php
class Student implements SplObserver{
protected $tipo = "Student";
private $nome;
private $endereco;
private $telefone;
private $email;
private $_classes = array();
public function GET_tipo() {
return $this->tipo;
}
public function GET_nome() {
return $this->nome;
}
public function GET_email() {
return $this->email;
}
public function GET_telefone() {
return $this->nome;
}
function __construct($nome) {
$this->nome = $nome;
}
public function update(SplSubject $object){
$object->SET_log("Comes from ".$this->nome.": I'm a student of ".$object->GET_materia());
}
}
?>
class TEACHER
<?php
class Teacher implements SplObserver{
protected $tipo = "Teacher";
private $nome;
private $endereco;
private $telefone;
private $email;
private $_classes = array();
public function GET_tipo() {
return $this->tipo;
}
public function GET_nome() {
return $this->nome;
}
public function GET_email() {
return $this->email;
}
public function GET_telefone() {
return $this->nome;
}
function __construct($nome) {
$this->nome = $nome;
}
public function update(SplSubject $object){
$object->SET_log("Comes from ".$this->nome.": I teach in ".$object->GET_materia());
}
}
?>
Class SUBJECT
<?php
class Subject implements SplSubject {
private $nome_materia;
private $_observadores = array();
private $_log = array();
public function GET_materia() {
return $this->nome_materia;
}
function SET_log($valor) {
$this->_log[] = $valor ;
}
function GET_log() {
return $this->_log;
}
function __construct($nome) {
$this->nome_materia = $nome;
$this->_log[] = " Subject $nome was included";
}
/* Adiciona um observador */
public function attach(SplObserver $classes) {
$this->_classes[] = $classes;
$this->_log[] = " The ".$classes->GET_tipo()." ".$classes->GET_nome()." was included";
}
/* Remove um observador */
public function detach(SplObserver $classes) {
foreach ($this->_classes as $key => $obj) {
if ($obj == $classes) {
unset($this->_classes[$key]);
$this->_log[] = " The ".$classes->GET_tipo()." ".$classes->GET_nome()." was removed";
}
}
}
/* Notifica os observadores */
public function notify(){
foreach ($this->_classes as $classes) {
$classes->update($this);
}
}
}
?>
Application
<?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>";
?>
OUTPUT
First Subject
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
)
Second Subject
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
)