(目录)
观察者模式是一种非常流行的设计模式,也常被叫作订阅-发布模式。 观察者模式在现代的软件开发中应用非常广泛,比如,商品系统、物流系统、监控系统、运营数据分析系统等。 常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。 当观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。
原理
原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。
这个定义中包含了两个前提条件:
- 被依赖的对象叫作被观察者,依赖的对象叫作观察者;
- 观察者观察被观察者的状态变化。
不过,这听上去还是有点太抽象,更容易理解下面这些不同的对于观察者模式的叫法:
- 发布者-订阅者;
- 生产者-消费者;
- 事件发布-事件监听。
不管怎么叫,这些模式在本质上都是观察者模式。
观察者模式 UML 图如下:
从该 UML 图中,观察者模式包含的四个关键角色:
- 发布者(Publisher):也被叫作主题、被订阅者、被观察者等,通常是指观察者关心的相关对象集合,比如,将 GitLab 上的 Git 库作为发布者,我们关心 Git 库的代码发生了变更后做特定的操作;
- 具体发布者(PublisherImpl):实现了发布者定义的方法的具体实现类。;
- 订阅者(Observer):也叫作观察者,它会存储一个注册列表,用于存放订阅者。当发布者发布消息或事件时,会通知到订阅者进行处理;
- 具体订阅者(ObserverImpl):实现具体定义的方法操作。
UML 对应的代码实现,是一种经典的实现方式,如下:
public interface IPublisher {
void addObserver(IObserver o);
void removeObserver(IObserver o);
void notify(double amt);
}
public interface IObserver {
void notify(String acct, double amt);
}
public class ObserverImpl implements IObserver {
@Override
public void notify(String acct, double amt) {
System.out.println("=== 接收到通知:账户:"+acct + " 账单:"+amt);
}
}
public class PublisherImpl implements IPublisher {
private String acct;
private double balance;
private List<IObserver> myObservers;
public PublisherImpl(String anAcct, double aBalance){
this.acct = anAcct;
this.balance = aBalance;
this.myObservers = new ArrayList<>();
}
@Override
public void addObserver(IObserver o) {
myObservers.add(o);
}
@Override
public void removeObserver(IObserver o) {
myObservers.remove(o);
}
@Override
public void notify(double amt) {
balance -= amt;
if (balance < 0) {
overdrawn();
}
}
private void overdrawn() {
for (IObserver o : myObservers) {
o.notify(acct, balance);
}
}
}
public class Demo {
public static void main(String[] args) {
IPublisher account = new PublisherImpl("TEST123", 10.00);
IObserver bill = new ObserverImpl();
account.addObserver(bill);
account.notify(11.00);
}
}
// 输出结果
// === 接收到通知:账户:TEST123 账单:-1.0
使用场景
观察者模式常见的使用场景有以下几种:
- **当一个对象状态的改变需要改变其他对象时。**比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
- **一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。**比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
- **需要创建一种链式触发机制时。**比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
- **微博或微信朋友圈发送的场景。**这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
- **需要建立基于事件触发的场景。**比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。
代码实现 消息订阅功能
实现一个简单的消息订阅通知的功能。
首先,定义观察者 IMessageObserver。它只有一个 update 方法,当有消息 Message 发送过来时就会调用该方法:
public interface IMessageObserver {
void update(Message m);
}
然后,定义一个被观察者 ISubject ,它定义了三个方法
- 增加观察者方法 attach;
- 删除观察者方法 detach ;
- 更新通知方法 notifyUpdate:
public interface ISubject {
/* 增加观察者 */
void attach(IMessageObserver o);
/* 删除观察者 */
void detach(IMessageObserver o);
/* 更新通知 */
void notifyUpdate(Message m);
}
接着,定义消息的具体数据结构 Message,这里使用一个 content 消息内容:
public class Message {
final String content;
public Message (String m) {
this.content = m;
}
public String getContent() {
return content;
}
}
再接下来,实现 ISubject 的具体发布者类 MessagePublisher,它持有一组观察者 MessageObserver 实例,可以通过 attach 和 detach 接口方法新增和删除观察者:
public class MessagePublisher implements ISubject {
private List<IMessageObserver> observers = new ArrayList<>();
@Override
public void attach(IMessageObserver o) {
observers.add(o);
}
@Override
public void detach(IMessageObserver o) {
observers.remove(o);
}
@Override
public void notifyUpdate(Message m) {
observers.forEach(x->x.update(m));
}
}
最后,实现三个具体订阅者类,它们都实现了 MessageObserver 接口,分别代表不同的消息接收后的对应处理操作:
public class MessageSubscriber1 implements IMessageObserver{
@Override
public void update(Message m) {
System.out.println("MessageSubscriber1 :: " + m.getContent());
}
}
public class MessageSubscriber2 implements IMessageObserver{
@Override
public void update(Message m) {
System.out.println("MessageSubscriber2 :: " + m.getContent());
}
}
public class MessageSubscriber3 implements IMessageObserver{
@Override
public void update(Message m) {
System.out.println("MessageSubscriber3 :: " + m.getContent());
}
}
运行一段单元测试,具体运行结果:
public class Client {
public static void main(String[] args) {
IMessageObserver s1 = new MessageSubscriber1();
IMessageObserver s2 = new MessageSubscriber2();
IMessageObserver s3 = new MessageSubscriber3();
ISubject p = new MessagePublisher();
p.attach(s1);
p.attach(s2);
/* s1 和 s2 会收到消息通知 */
p.notifyUpdate(new Message("First Message"));
p.detach(s1);
p.attach(s3);
/* s2 和 s3 会收到消息通知 */
p.notifyUpdate(new Message("Second Message"));
}
}
// 输出结果
// MessageSubscriber1 :: First Message
// MessageSubscriber2 :: First Message
// MessageSubscriber2 :: Second Message
// MessageSubscriber3 :: Second Message
观察者模式使用场景的特点在于找到合适的被观察者,定义一个通知列表,将需要通知的对象放到这个通知列表中,当被观察者需要发起通知时,就会通知这个列表中的所有“人”。
使用观察者模式的理由
使用观察者模式的原因,主要有以下两个:
- 为了方便捕获观察对象的变化并及时做出相应的操作。 观察者模式对于对象自身属性或行为发生变化后,需要快速应对的应用场景尤其有效,比如:
购物包裹的运送。因为商品变成包裹的形式后会出现各种各样的状态变化(配送中、接收中、退货中等),这些状态变化非常快,并且每一个状态都会对应一系列连锁的操作,这时使用观察者模式就非常方便,能够通知需要知道这些状态变化的系统,并做相应的处理,比如,物流监控、性能监控、运营系统等;
- 为了提升代码扩展性。 如果不使用观察者模式来捕获一个被观察对象的属性变化,那么就需要在被观察对象执行代码逻辑中加入调用通知某个对象进行变更的逻辑,这样不仅增加了代码的耦合性,也让代码扩展变得非常困难,因为要想新增一个新的观察对象,就需要修改被观察对象的代码,这样的扩展性非常低。相反,使用观察者模式则只需要将观察者对象注册到被观察者存储的观察者列表中就能完成代码扩展。
优缺点
观察者模式主要有以下三个优点:
- 降低系统与系统之间的耦合性。 比如,建立以事件驱动的系统,能创建更多观察者与被观察者,对象之间相互的关系比较清晰,可以随时独立添加或删除观察者;
- 提升代码扩展性。 由于观察者和被观察之间是抽象耦合,所以增删具体类并不影响抽象类之间的关系,这样不仅满足开闭原则,也满足里氏替换原则,能够很好地提升代码扩展性;
- 可以建立一套基于目标对象特定操作或数据的触发机制。 比如,基于消息的通知系统、性能监控系统、用户数据跟踪系统等,观察者通过特定的事件触发或捕获特定的操作来完成一系列的操作。
观察者模式的缺点如下:
- **增加代码的理解难度。 **由于使用组合关系,被观察者和观察者之间属于松散关系,所以在代码逻辑的理解上,就需要提前搞清楚上下文中有哪些中间类或调用逻辑关系,这样才能正确理解逻辑;
- **降低了系统性能。 **观察者模式通常需要事件触发,当观察者对象越多时,被观察者需要通知观察者所花费的时间也会越长,这样会在某种程度上影响程序的效率。
总结
标签:Observer,对象,void,观察者,模式,Message,设计模式,public From: https://blog.51cto.com/panyujie/7438142观察者模式在原理上是一个比较抽象的模式,但是业界根据不同的应用场景和需求总结出了很多不同的实现方式,这让观察者模式变得易于使用。 在观察者模式中,被观察者通常会维护一个观察者列表。当被观察者的状态发生改变时,就会通知观察者。 观察者模式现在被大量应用在分布式系统的开发中。 从本质上来讲,观察者模式描述了如何建立对象与对象之间一种简单的依赖关系。 在现实中,最为常见的观察者模式的例子就是订阅微信公众号,当公众号发布一篇新文章时,能在微信上收到消息,然后选择合适的时间打开阅读。 因此,在使用观察者模式时,要重点关注哪些变化是需要从被观察对象那里捕获(观察到)并进行下一步处理的,一方面不能盲目捕获所有的变化,另一方面也不能遗漏重要的变化。换句话说,找到合适的变化并进行正确的处理才是使用观察者模式的正确打开方式。