一、技术背景与应用场景
观察者模式,也被称为订阅-发布模式,在现代软件开发中占据着核心地位,广泛应用于商品系统、物流系统、监控系统、运营数据分析系统等众多领域。在事件驱动架构的设计中,观察者模式更是扮演了关键角色。当对象的状态改变时,这一变化被视为一个事件,观察者通过处理这些事件来执行相应的操作。
使用观察者模式的原因主要有两方面:
-
实时响应状态变化:该模式有助于快速响应被观察对象的属性或行为变化。例如,在商品配送过程中,包裹状态会经历配送中、接收中、退货中等多个阶段,每一个状态变更都可能触发一系列关联操作。观察者模式能够有效地将状态变更通知给物流监控、性能监控和运营系统等需了解变更的对象,确保及时应对并做出相应处理。
-
提升代码可扩展性:传统的耦合式设计中,若要使某个对象知道另一个对象的状态变化,往往需要直接在被观察对象的逻辑中插入通知调用。这种做法增加了代码耦合度,并使得新增观察者时必须修改原有代码,降低了系统的扩展性。相反,观察者模式允许观察者通过注册自身到被观察者的观察者列表中,无需修改被观察者代码即可实现灵活扩展。
典型应用场景包括但不限于:
- 商品库存量发生变化时,自动更新商品详情页及购物车信息。
- 微信公众号文章推送,发布者无需关心具体订阅用户,只需发出通知。
- 创建链式触发机制,如A对象的行为引发B对象的响应,进而影响C对象……
- 社交媒体平台如微博或微信朋友圈,用户发布动态后,关注者会接收到通知。
- 基于事件驱动编程,如Java UI框架中的键盘和鼠标事件,由监听器对象统一处理。
二、观察者模式定义与结构
观察者模式的核心是建立了一种一对多的依赖关系,使得当一个对象(被观察者)状态变化时,所有依赖它的对象(观察者)都能得到自动通知和更新。
观察者模式包含四个关键组成部分:
- 被观察者(Publisher/Subject):即发布者,是观察者关注的对象集合,如GitLab上的Git库,当其内容发生变更时,相关操作随之启动。
- 具体被观察者(PublisherImpl/SubjectImpl):实现了被观察者接口的方法,负责维护观察者列表以及在状态变化时通知观察者。
- 观察者(Observer):对被观察者状态感兴趣的对象,被存储在被观察者的注册列表中,以便在被通知时执行特定操作。
- 具体观察者(ObserverImpl):实现观察者接口的具体类,对接收到的通知进行实际的业务处理。
三、使用步骤举例
以账单监控和消息订阅为例说明观察者模式的使用:
-
账单监控示例
-
定义了一个
Publisher
接口,其中包含添加和删除观察者方法以及通知余额变动的方法。public interface Publisher { void addObserver(Observer o); void removeObserver(Observer o); void notify(double amt); }
-
具体实现类
PublisherImpl
维持了一个观察者列表,当账户余额减少且出现透支时,遍历并通知所有观察者。public class PublisherImpl implements Publisher{ private String acct; private double balance; private List<Observer> myObservers; public PublisherImpl(String anAcct, double aBalance) { acct = anAcct; balance = aBalance; myObservers = new ArrayList(); } @Override public void addObserver(Observer o) { myObservers.add(o); } @Override public void removeObserver(Observer o) { myObservers.remove(o); } @Override public void notify(double amt) { balance -= amt; if(balance < 0) { overdrawn(); } } private void overdrawn() { for (Observer o: myObservers) { o.notify(acct, balance); } } }
-
Observer
接口定义了处理账单通知的方法public interface Observer { void notify(String acct, double amt); }
-
ObserverImpl
实现了这个方法,打印出具体的账单信息。public class ObserverImpl implements Observer{ @Override public void notify(String acct, double amt) { System.out.println("====== 接收到通知:账户: " + acct + " 账单:" + amt); } }
-
测试代码
public class Demo { public static void main(String[] args) { PublisherImpl account = new PublisherImpl("test123", 10); ObserverImpl bill = new ObserverImpl(); account.addObserver(bill); account.notify(11); } }
-
测试结果
-
-
消息订阅示例
-
定义了
MessageObserver
接口,其中有一个update
方法用于接收消息通知。public interface MessageObserver { void update(Message m); }
-
设计了一个
Subject
接口,包含增加和删除观察者以及发送消息更新的方法。public interface Subject { void attach(MessageObserver o); //增加观察者 void detach(MessageObserver o); //删除观察者 void notifyUpdate(Message m); //更新通知 }
-
消息对象
public class Message { final String content; public Message (String m) { this.content = m; } public String getContent() { return content; } }
-
实现了
MessagePublisher
作为具体的消息发布者,持有观察者列表并在有新消息时遍历通知所有观察者。public class MessagePublisher implements Subject { private List<MessageObserver> observers = new ArrayList<>(); @Override public void attach(MessageObserver o) { observers.add(o); } @Override public void detach(MessageObserver o) { observers.remove(o); } @Override public void notifyUpdate(Message m) { observers.forEach(x->x.update(m)); } }
-
创建了三个不同的
MessageSubscriber
实现类,它们各自实现了消息接收到后的处理逻辑。public class MessageSubscriber1 implements MessageObserver { @Override public void update(Message m) { System.out.println("MessageSubscriber1 :: " + m.getContent()); } } public class MessageSubscriber2 implements MessageObserver { @Override public void update(Message m) { System.out.println("MessageSubscriber2 :: " + m.getContent()); } } public class MessageSubscriber3 implements MessageObserver { @Override public void update(Message m) { System.out.println("MessageSubscriber3 :: " + m.getContent()); } }
-
测试代码
public class Client { public static void main(String[] args) { MessageObserver s1 = new MessageSubscriber1(); MessageObserver s2 = new MessageSubscriber2(); MessageObserver s3 = new MessageSubscriber3(); Subject p = new MessagePublisher(); p.attach(s1);// p.attach(s2); p.notifyUpdate(new Message("First Message")); //s1和s2会收到消息通知 p.detach(s1); p.attach(s3); p.notifyUpdate(new Message("Second Message")); //s2和s3会收到消息通知 } }
-
测试结果
-
四、优缺点分析
观察者模式的优点在于:
- 降低耦合性:它促进了基于事件驱动的松散耦合系统构建,使得各个模块独立性强,易于扩展。
- 增强代码扩展性:抽象的观察者与被观察者关系让系统能轻易地添加或移除观察者,遵循开闭原则,符合里氏替换原则,提高了代码的扩展性。
- 建立触发机制:通过特定事件触发观察者的一系列操作,适用于消息通知系统、性能监控系统等多种场景。
然而,观察者模式也存在一些挑战:
- 复杂性增加:由于观察者和被观察者之间是组合而非继承关系,理解程序逻辑时需要理清各组件之间的交互和依赖。
- 性能开销:随着观察者数量的增长,通知所有观察者所需的时间也会相应增加,可能影响整体系统效率。
总结
尽管观察者模式在理论上具有一定的抽象性,但通过不同应用场景下的实践和变体,开发者已经将其转化为易于理解和使用的模式。在观察者模式中,被观察者通常维护一个观察者列表,当状态变化时便通知列表中的观察者执行相应的动作。
现实生活中,诸如微信公众号订阅的例子完美体现了观察者模式的工作原理。因此,在使用观察者模式时,关键在于识别哪些状态变化是至关重要的,既要避免过度捕获无关的变化,也要确保不会遗漏重要状态的转换。只有这样,才能充分利用观察者模式的优势,高效构建适应需求变化的应用系统。