观察者模式
定义:
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在GoF的《设计模式》一书中,它的定义是这样的:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖对象的对象都会自动收到通知。
应用场景:
以下情况可以考虑使用观察者模式:
- 一个对象的改变会导致多个对象发生改变,而并不知道具体有多少对象将会发生改变,也不知道这些对象是谁;
- 需要在系统中创建一个触发链,使得事件拥有跨域通知
设计思路
观察者模式是一个比较抽象的模式,根据不同的应用场景和需求,有完全不同的实现方式。我们先看下其中最经典的一种实现方式,具体代码如下所示:
1 | interface Subject { |
运行结果如下:
1 | Observer 1: State 1 |
原理和代码实现都非常简单,也比较好理解,不需要过多解释,下面还是通过一个具体的例子来重点讲下,什么情况下需要用到这种设计模式?或者是这种设计模式能解决什么问题呢?
假设我们在开发一个电商系统,用户注册成功后,我们会给用户发送新人优惠券,代码大概下面这个样子:
1 | class UserController { |
此时注册接口做了两件事,用户注册和发放优惠券,违反了单一原则,但是,如果没有扩展和修改需求,现在的代码还是可以接受的。如果非得用观察者模式,就需要引入更多的类和更加复杂的代码结构,反倒是一种过度设计了。
相反地,如果需求频繁变动,比如,用户注册成功之后,不再发送优惠券,而是改为赠送会员积分,并且要求给用户发送一条“欢迎注册成功”的短信。这种情况下,我们就需要频繁修改register()函数中的代码了,违反了开闭原则。而且,如果注册用户之后需要执行的后续操作越来越多,那么register()函数的逻辑就会越来越复杂,也就影响了代码的可读性和可维护性。
这个时候,观察者模式就派上用场了。利用观察者模式,我对上面代码进行了重构。重构之后的代码如下所示:
1 | interface RegObserver { |
当我们需要添加新的观察者的时候,比如,用户注册成功之后,默认给用户创建一个虚拟钱包,基于观察者模式的代码实现,UserController类的register()函数完全不需要修改,只需要再添加一个实现了RegObserver接口的类,并且注册到UserController的观察者列表即可。
不过,你可能会说,如果把发送短信替换为发送站内信的时候,需要修改RegPromotionObserver中的handleRegSuccess()函数代码,这还是违反了开闭原则呀?你说的没错,不过,相对于register()函数来说,handleRegSuccess()的函数逻辑要简单很多,修改更不容易出错,引入bug的风险更低。
实际上,设计模式干的事情就是解耦。创建型模式是将创建和使用解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚低耦合等特征,以此来控制和应对代码的复杂性,提高代码的可扩展性。