观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象(通常被称为主题或者被观察者)的状态发生变化时,所有依赖它的对象(观察者)都会得到通知并自动更新。观察者模式主要用于实现分布式系统,事件驱动系统和数据绑定等场景。
角色
观察者模式的类结构图:
观察者模式包含以下几个主要角色
1:Subject(目标主题):被观察的目标主题的接口抽象,维护观察者对象列表,并定义注册方法register()(订阅)与通知方法notify()(发布)。
2:ConcreteSubject(主题实现):被观察的目标主题的具体实现类。
3: Observer(观察者):观察者的接口抽象,定义响应方法update()。
4: ConcreteObserver(观察者实现):观察者的具体实现类,可以有任意多个子类实现。实现了响应方法update(),收到通知后进行自己独特的处理。
工作流程
1:观察者对象将自己注册到被观察者对象的观察者列表中,表明它对被观察者的状态感兴趣
2:当被观察者的状态发生改变时,它会遍历观察者列表,并逐个调用观察者的更新方法,通知它们状态已经改变
3:当观察者接收到通知后,会指向相应的更新操作,以保持与被观察者状态的同步
优点跟缺点
优点:
1:支持松耦合:被观察者跟观察者的依赖关系是抽象的,它们可以独立地进行修改和扩展,而不会相互影响
2:动态添加和删除观察者:观察者可以随时注册和注销,使得系统可以灵活地响应需求变化。
缺点:
1:通知所有观察者可能导致效率降低,特别是在观察者数量较多的情况下
2:如果存在循环依赖,可能导致观察者和被观察者间的死循环,因此,在实现观察者模式时,需要注意避免循环依赖的问题。
实现方式
1:推模型
被观察者主动向观察者推送所需的数据。这种方式的优点是观察者可以直接使用数据,无需自己获取。但缺点是,如果观察者并不需要所有数据,这样会导致数据传输的浪费。
2:拉模型
被观察者仅通知观察者发生了变化,由观察者自己决定是否需要获取数据。这种方式的优点是避免了不必要的数据传输,但缺点是观察者需要额外的操作来获取数据
实际工作应用场景
1:事件驱动系统
在事件驱动的系统中,当某个事件发生时,需要通知其他组件执行相应的操作。例如,在web应用中,用户注册成功后,需要发送欢迎邮件并记录注册日志。使用观察者模式,可以将邮件发送和日志记录作为观察者,将用户注册作为被观察者。当用户注册成功时,通知观察者执行相应的操作。
2:实时数据更新
实时数据更新场景,例如股票行情。使用观察者模式,可以使得当股票价格发生变化时,所有订阅了该股票的客户端(观察者)得到通知并更新数据。在这种情况下,股票行情是被观察者,客户端是观察者。
3:模型-视图-控制器(MVC)架构
MVC架构是一种常见的软件架构模式,其中模型(Model)负责管理数据和业务逻辑,视图(View)负责展示数据,控制器(Controller)负责接收用户输入并更新模型。在这种架构中,可以使用观察者模式使得当模型发生变化时,通知视图更新。模型是被观察者,视图是观察者。
4:配置管理
在许多应用程序中,需要对配置文件进行监控,以便在配置发生变化时,实时更新程序的运行状态。在这种情况下,可以使用观察者模式,将配置文件作为被观察者,将依赖配置文件的组件作为观察者。当配置文件发生变化时,通知观察者更新其状态。
代码实现
1:定义观察者接口(Observer)
interface Observer
{
public function update($subject);
}
2:定义被观察者接口(Subject)
interface Subject
{
public function registerObserver(Observer $observer);
public function removeObserver(Observer $observer);
public function notifyObservers();
}
3:实现一个具体的被观察者(WeatherData):
class WechatData implements Subject
{
private $observers;
private $temperature;
private $humidity;
private $pressure;
public function __construct()
{
$this->observers = [];
}
public function registerObserver(Observer $observer)
{
$this->observers[] = $observer;
}
public function removeObserver(Observer $observer)
{
$index = array_search($observer, $this->observers);
if ($index !== false) {
unset($this->observers[$index]);
}
}
public function notifyObservers()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setMeasurements($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->pressure = $pressure;
$this->notifyObservers();
}
public function getTemperature()
{
return $this->temperature;
}
public function getHumidity()
{
return $this->humidity;
}
public function getPressure()
{
return $this->getPressure;
}
}
4:定义一个具体的观察者
class CurrentConditionsDisplay implements Observer
{
private $temperature;
private $humidity;
public function __construct(Subject $wechatData)
{
$wechatData->registerObserver($this);
}
public function update($subject)
{
if ($subject instanceof WechatData) {
$this->temperature = $subject->getTemperature();
$this->humidity = $subject->getHumidity();
$this->display();
}
}
public function display()
{
echo "Current conditions:" . $this->temperature . "°C and " . $this->humidity . "% humidity" . PHP_EOL;
}
}
5:开始使用
$wechatData = new WechatData();
$currentConditionsDisplay = new currentConditionsDisplay($wechatData);
$wechatData->setMeasurements(25, 65, 1013);
$wechatData->setMeasurements(22, 70, 1012);
这个例子中,WeatherData
是一个具体的被观察者,它实现了 Subject
接口,当气象数据发生变化时,它会通知所有注册的观察者。CurrentConditionsDisplay
是一个具体的观察者,它实现了 Observer
接口,当被通知气象数据发生变化时,它会更新自己的状态并显示当前的温度和湿度。
PHP框架的观察者模式
在很多PHP常用的框架中,都使用到了观察者模式,那么我们以Yii框架为例:
Yii 框架中的观察者模式主要体现在事件处理机制上。当某个类的特定事件发生时,它可以触发其他类(事件处理器)的方法。这种机制类似于观察者模式中的被观察者和观察者之间的关系。
在 Yii 中,可以使用如下方法为类绑定事件处理器:
$component->on($eventName, $handler);
其中 $component
是触发事件的类(被观察者),$eventName
是事件名称,$handler
是事件处理器(观察者)。
当 $component
的特定事件发生时,Yii 会自动调用与之关联的所有事件处理器。
更多信息请查看https://www.yiiframework.com/doc/guide/2.0/zh-cn/concept-events
下面我们来写一个简单的Yii事件处理示例:
1:定义一个自定义事件
class CustomEvent extends Event
{
public $message;
}
2:创建一个类(被观察者)
class MyClass extends Component
{
const EVENT_MY_CUSTOM_EVENT = ‘myCustomEvent’;
public function triggerCustomEvent()
{
$event = new CustomEvent();
$event->message = "hello Observer";
$this->trigger(self::EVENT_MY_CUSTOM_EVENT, $event);
}
}
3:创建一个事件处理器(观察者)
class MyEventHandler
{
public function handleCustomEvent(CustomEvent $event)
{
echo "Event trigger with message:" . $event->message;
}
}
4:使用
$myObject = new MyClass();
$myEventHandler = new MyEventHandler();
$myObject->on(MyClass::EVENT_MY_CUSTOM_EVENT, [$myEventHandler, ’handleCustomEvent’]);
$myObject->triggerCustomEvent();
在这个示例中,我们创建了一个 MyClass
类(被观察者)和一个 MyEventHandler
事件处理器(观察者)。MyClass
类定义了一个名为 EVENT_MY_CUSTOM_EVENT
的自定义事件。然后,我们将 MyEventHandler
的 handleCustomEvent
方法绑定到 MyClass
的自定义事件上。最后,当 MyClass
的自定义事件被触发时,MyEventHandler
中的 handleCustomEvent
方法会自动执行。
题外话:相信大家在用到yii框架的时候,肯定会很熟悉beforeSave
和 afterSave
方法,那么这两个方法是否是观察者模式呢?因为它们是观察到有save操作而触发的动作。从本质上来说,这两个方法可以算做钩子方法,因为它们是直接在基类(ActiveRecord)定义的,而不是完全独立的观察者类。
触发模式
观察者模式可以看作是一种触发模式。当被观察者(如A操作)发生变化时,它会通知所有依赖于它的观察者(如B操作和C操作),这样就实现了A操作触发B操作和C操作的效果。
观察者模式的优势在于它实现了被观察者和观察者之间的松耦合。这意味着你可以在不修改被观察者的情况下,添加或删除观察者。因此,观察者模式不仅实现了触发关系,还保持了系统的灵活性和可扩展性。
参考书籍
《秒懂设计模式》
《Head First 设计模式》