以书中的气象监测应用为例:现在有一个气象中心可以监测温度、湿度、气压三种数据,我们需要通过 WeatherData
对象来获取这些数据,然后将这些数据显示在特定的装置上。
WeatherData
拥有以下方法:
getTemperature()
:获取温度数据getHumidity()
: 获取湿度数据getPressure()
:获取气压数据measurementsChanged()
:一旦气象站更新数据,这个方法会被调用这样一看似乎十分简单:我们只要在 measurementsChanged()
中通过一系列 getter
获取到气象台提供的温度、湿度与气压数据,然后再调用显示装置的更新数据方法即可。
但是,如果我们后续需要增加或减少显示装置应该怎么办呢?每次都要修改 measurementsChanged()
显然不是个好办法。
想想在现实生活中我们是怎么享受报纸订阅服务的?
气象站与显示装置之间其实也是这样的关系,气象站为「出版者」,显示装置为「订阅者」:「需要获得气象站数据的显示装置可以向气象站申请「订阅」,这样一旦有气象数据更新,气象站就会通知申请订阅的显示装置;如果显示装置不再需要该气象站提供数据,则可以「取消订阅」,不再接受气象站的通知」。
上述「出版者」称为「主题」(Subject),「订阅者」称为「观察者」(Observer),两者构成了观察者模式的主要部分。
观察者模式定义如下:
❝观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。 ❞
观察者模式类图
类图中包含两个接口定义:
Subject
:registerObserver()
:添加订阅者removeObserver()
:移除订阅者notifyObserver()
:通知订阅者Observer
:update()
:在主题 notifyObserver()
中被调用,用于更新观察者的数据根据上述观察者模式定义,我们先为气象站设计「主题」与「观察者」两个接口,除此之外也可以添加一个显示装置接口,专门负责显示装置的具体显示格式。
接口定义好后,就可以让具体的类来实现这些接口了:
WeatherData
作为具体主题,实现 Subject
主题接口Observer
观察者接口气象站类图
<?php
/**
* 主题接口
*/
interface Subject
{
public function registerObserver($observer);
public function removeObserver($observer);
public function notifyObservers();
}
/**
* 观察者接口
*/
interface Observer
{
public function update($temp, $humidity, $pressure);
}
/**
* 显示装置显示接口
*/
interface DisplayElement
{
public function display();
}
/**
* WeatherData 实现主题接口
*/
class WeatherData implements Subject
{
// 观察者数组
private $observers;
// 温度
private $temperature;
// 湿度
private $humidity;
// 气压
private $pressure;
public function __construct()
{
$this->observers = [];
}
/**
* 加入新的观察者
*/
public function registerObserver($observer)
{
$this->observers[] = $observer;
}
/**
* 移除观察者
*/
public function removeObserver($observer)
{
$index = array_search($observer, $this->observers);
unset($this->observers[$index]);
}
/**
* 通知观察者
*/
public function notifyObservers()
{
// 遍历数组通知观察者
foreach ($this->observers as $observer) {
$observer->update($this->temperature, $this->humidity, $this->pressure);
}
}
public function measurementsChanged()
{
// 通知订阅者
$this->notifyObservers();
}
/**
* 气象站有新数据将调用该函数
*/
public function setMeasurements($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->pressure = $pressure;
$this->measurementsChanged();
}
}
// 创建显示装置
/**
* 显示装置 1: 只显示温度和湿度
*/
class FirstDisplay implements Observer, DisplayElement
{
// 温度
private $temperature;
// 湿度
private $humidity;
/**@var WeatherData $weatherData */
private $weatherData;
public function __construct($weatherData)
{
// 创建一个 WeatherData 实例
$this->weatherData = $weatherData;
$this->weatherData->registerObserver($this);
}
public function update($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->display();
}
public function display()
{
echo "当前温度:{$this->temperature},当前湿度:{$this->humidity}\n";
}
}
/**
* 显示装置 2:只显示气压
*/
class SecondDisplay implements Observer, DisplayElement {
// 气压
private $pressure;
/**@var WeatherData $weatherData */
private $weatherData;
public function __construct($weatherData)
{
// 创建一个 WeatherData 实例
$this->weatherData = $weatherData;
$this->weatherData->registerObserver($this);
}
public function update($temperature, $humidity, $pressure)
{
$this->pressure = $pressure;
$this->display();
}
public function display()
{
echo "当前气压:{$this->pressure}\n";
}
}
/**
* 显示装置 3(略)
* class ThirdDisplay
*/
// 测试调用
// 创建一个 WeatherData 对象
$weatherData = new WeatherData();
// 创建显示装置 1,传入 WeatherData 对象
$firstDisplay = new FirstDisplay($weatherData);
// 传入模拟气象数据
$weatherData->setMeasurements(80, 70, 30.4);
$weatherData->setMeasurements(70, 60, 29.2);
// 取消订阅
$weatherData->removeObserver($firstDisplay);
// 创建显示装置 2,传入 WeatherData 对象
$secondDisplay = new SecondDisplay($weatherData);
$weatherData->setMeasurements(90, 60, 29.2);
/* Output:
当前温度:80,当前湿度:70
当前温度:70,当前湿度:60
当前气压:29.2
*/
class Subject:
def __init__(self):
"""
主题(出版者)
"""
self._observers = []
def register(self, observer):
"""
添加观察者
"""
if observer not in self._observers:
self._observers.append(observer)
def remove(self, observer):
"""
移除观察者
"""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self):
"""
发送通知给所有观察者
"""
for observer in self._observers:
observer.update()
class WeatherData(Subject):
def __init__(self):
Subject.__init__(self)
self._temperature = 0 # 温度
self._humidity = 0 # 湿度
self._pressure = 0 # 气压
def set_measurements(self, temperature, humidity, pressure):
"""
气象数据发生变动时调用该函数
"""
self._temperature = temperature
self._humidity = humidity
self._pressure = pressure
self.notify()
@property
def temperature(self):
return self._temperature
@property
def humidity(self):
return self._humidity
@property
def pressure(self):
return self._pressure
class FirstDisplay:
def __init__(self, weatherData):
"""
显示装置 1:显示温度和湿度
"""
self._weather_data = weatherData
self._weather_data.register(self)
self._temperature = 0
self._humidity = 0
def update(self):
"""
更新数据
"""
self._temperature = self._weather_data.temperature
self._humidity = self._weather_data.humidity
self.display()
def display(self):
"""
显示数据
"""
print("当前温度:%s,当前湿度:%s" % (self._temperature, self._humidity))
class SecondDisplay:
def __init__(self, weatherData):
"""
显示装置 2:显示气压
"""
self._weather_data = weatherData
self._weather_data.register(self)
self._pressure = 0
def update(self):
"""
更新数据
"""
self._pressure = self._weather_data.pressure
self.display()
def display(self):
print("当前气压:%s" % self._pressure)
def main():
# 创建一个 WeatherData 对象
weather_data = WeatherData()
# 创建显示装置 1
first_display = FirstDisplay(weather_data)
# 传入模拟数据
weather_data.set_measurements(21, 50, 3)
weather_data.set_measurements(3, 70, 4)
# 移除
weather_data.remove(first_display)
# 添加装置 2
second_display = SecondDisplay(weather_data)
weather_data.set_measurements(21, 50, 30)
if __name__ == "__main__":
main()
"""
Output:
当前温度:21,当前湿度:50
当前温度:3,当前湿度:70
当前气压:30
"""
package main
import (
"fmt"
)
// Subject 主题
type Subject interface {
Register(observer Observer)
Remove(obeserver Observer)
Notify()
}
// WeatherData 具体主题
type WeatherData struct {
observers []Observer
temperature int
humidity int
pressure float32
}
// 注册
func (w *WeatherData) Register(observer Observer) {
w.observers = append(w.observers, observer)
}
// 取消订阅
func (w *WeatherData) Remove(observer Observer) {
// 双指针法:找到需要取消订阅的对象并覆盖
j := 0
for _, ob := range w.observers {
if ob != observer {
w.observers[j] = observer
j++
}
}
w.observers = w.observers[:j]
}
// 通知所有订阅者
func (w *WeatherData) Notify() {
for _, observer := range w.observers {
observer.update(w.temperature, w.humidity, w.pressure)
}
}
// 设置新的数据
func (w *WeatherData) SetMeasurements(temperature int, humidity int, pressure float32) {
w.temperature = temperature
w.humidity = humidity
w.pressure = pressure
w.Notify()
}
// Observer 观察者
type Observer interface {
update(temperature int, humidity int, pressure float32)
display()
}
// FirstDisplay 显示装置 1
type FirstDisplay struct {
temperature int
humidity int
pressure float32
}
func (display *FirstDisplay) update(temperature int, humidity int, pressure float32) {
display.temperature = temperature
display.humidity = humidity
display.pressure = pressure
display.display()
}
func (display *FirstDisplay) display() {
fmt.Printf("当前温度:%d, 当前湿度:%d\n", display.temperature, display.humidity)
}
// SecondDisplay 显示装置 2
type SecondDisplay struct {
temperature int
humidity int
pressure float32
}
func (display *SecondDisplay) update(temperature int, humidity int, pressure float32) {
display.temperature = temperature
display.humidity = humidity
display.pressure = pressure
display.display()
}
func (display *SecondDisplay) display() {
fmt.Printf("当前气压:%.2f\n", display.pressure)
}
func main() {
weatherData := WeatherData{}
// 创建显示装置 1
firstDisplay := &FirstDisplay{}
weatherData.Register(firstDisplay)
weatherData.SetMeasurements(23, 50, 23.1)
// weatherData.Remove(firstDisplay)
// 创建显示装置 2
secondDisplay := &SecondDisplay{}
weatherData.Register(secondDisplay)
weatherData.SetMeasurements(22, 70, 24.2)
}