观察者模式
观察者模式,在程序开发中最通俗的解释就是,观察一个对象的行为,当达到预期的某个状态时需要做什么动作。它还有个这名称叫做发布订阅模式,当一个对象改变了状态,会通知与它相关角色做相应的业务。
这里通过一个需求来演示观察者模式的具体设计逻辑,如有一个线上商城项目当商品成交后会立即已短信的形式将商品的订单信息发送至用户手机,用户通过短信得知订单的详细信息。
首先我们通过类图,设计该需求需要哪些类。
以上类图,我们定义了IMall商城接口它可以执行商品下单及付款的操作,并使用Mall类来实现它。还定义了一个短信接口它可以发送消息,并使用Sms类来实现它。另外Mall还依赖了Sms类,需要使用Sms类来发送消息。
代码示例
-
IMall商城接口代码
public interface IMall { void order(); void buy(); }
-
Mall商城实现类
public class Mall implements IMall { private ISms sms; public Mall(ISms sms) { this.sms = sms; } @Override public void order() { System.out.println("【商城服务】用户下单"); sms.sendMsg("用户下单成功"); } @Override public void buy() { System.out.println("【商城服务】用户支付"); sms.sendMsg("用户支付成功"); } }
-
ISms短信发送接口
public interface ISms { void sendMsg(String msg); }
-
Sms短信发送具体实现类
public class Sms implements ISms { @Override public void sendMsg(String msg) { System.out.println("【短信服务】发送订单消息:" + msg); } }
-
测试类
public class TestAction { public static void main(String[] args) { // 定义消息发送器 ISms sms = new Sms(); // 定义商城,并依赖短信发送服务 IMall mall = new Mall(sms); // 开始执行下单和支付 mall.order(); mall.buy(); } }
-
打印结果
【商城服务】用户下单 【短信服务】发送订单消息:用户下单成功 【商城服务】用户支付 【短信服务】发送订单消息:用户支付成功
通过上述例子我们实现了一个最简单的观察者模式,不难看出我们的程序分为了两个部分,一部分是执行业务的部分,一部分是在业务达到某个状态的时候执行某个动作的。商城类中下单和支付就是我们的业务部分,短信发送就是执行部分。带入到观察者模式中来,短信发送就属于是观察者,他时刻观察着业务执行的动向,当下单成功后就开始执行发送短信的工作了。
多观察者模式
通过上述的代码示例我们简单的了解了观察者模式大概的概念,但在实际的开发过程中,业务往往不会这么单一。比如现在需求又变了。当用户下单后不仅仅需要短信通知还需要同时发送微信消息及邮件消息。
这时我们可能想到的是,在Mall类中有Sms对象的基础上再次增加Email,Wechat两个对象然后通过调用发送消息的方式来进行通知。这样做时可以实现业务需求的,但是在程序开发上严重违反了里氏替换原则、和开闭原则。
实际上我们可以对现有程序做如下修改,先修改类图。
我们做了如下修改,将原有的短信发送接口再次抽象成了消息发送的接口,增加了邮箱(Email)微信(Wechat)实现类。
以下是具体代码的示例
-
修改商城类,它拥有多个观察者
public class Mall implements IMall { private List<IMsg> msgs = new ArrayList<>(); // 增加一个消息发送观察者 @Override public void addMsg(IMsg msg){ msgs.add(msg); } // 删除一个消息发送观察者 @Override public void delMsg(IMsg msg){ msgs.remove(msg); } // 通知所有的消息观察者,开始发送消息 @Override public void notifyMsgs (String msg){ for (IMsg iMsg : msgs) { iMsg.sendMsg(msg); } } @Override public void order() { System.out.println("【商城服务】用户下单"); this.notifyMsgs("用户支付成功"); } @Override public void buy() { System.out.println("【商城服务】用户支付"); this.notifyMsgs("用户支付成功"); } }
-
增加Email邮箱类,用来发送邮箱。微信类与之类似
public class Email implements IMsg { @Override public void sendMsg(String msg) { System.out.println("【邮箱服务】发送订单消息:" + msg); } }
-
测试类
public class TestAction { public static void main(String[] args) { // 定义消息发送器 IMsg sms = new Sms(); IMsg email = new Email(); IMsg wechat = new Wechat(); // 定义商城,并依赖短信发送服务 IMall mall = new Mall(); mall.addMsg(sms); mall.addMsg(email); mall.addMsg(wechat); // 开始执行下单和支付 mall.order(); mall.buy(); } }
-
打印日志
【商城服务】用户下单 【短信服务】发送订单消息:用户支付成功 【邮箱服务】发送订单消息:用户支付成功 【微信服务】发送订单消息:用户支付成功 【商城服务】用户支付 【短信服务】发送订单消息:用户支付成功 【邮箱服务】发送订单消息:用户支付成功 【微信服务】发送订单消息:用户支付成功
以上代码我们通过商城类(Mall)中msgs集合的方式来存放多个观察者的容器,并提供了观察者的注册与删除的功能。当要通知观察者只需动作的时候调用notifyMsgs方法,将通知到所有消息观察者中的每一个成员。这与我们所使用的消息中间件(如Rabbit Mq)的设计基本原理是一致的。所以前文所说的观察者模式还有一个更加形象的名称叫做发布订阅模式。例子中商城属于发布者,各个消息服务属于订阅者。
Java的最佳实现
观察者模式使用场景也是非常的多。只要在开发过程中碰到一个业务的某个动作之后会触发很多关联的服务做相应的业务流程的都可以使用观察者模式的结构。实现起来也非常简单。另外介于观察者模式使用场景的固定性,Java从一开始就提供了这种模式可供拓展的父类。即java.util.Observable类。我们可以查看JDK文档看看这个类。
实际上通过以上我们已经写好的例子,再将其改造为使用Java提供的类来实现也是很方便的。
如下面代码
-
修改商城类,增加实现java的Observable类。
public class Mall extends Observable implements IMall{ @Override public void order() { System.out.println("【商城服务】用户下单"); super.setChanged(); this.notifyObservers("用户支付成功"); } @Override public void buy() { System.out.println("【商城服务】用户支付"); super.setChanged(); this.notifyObservers("用户支付成功"); } }
-
修改各个消息服务类,增加一个Observer实现。
public class Sms implements Observer, IMsg { @Override public void sendMsg(String msg) { System.out.println("【短信服务】发送订单消息:" + msg); } @Override public void update(Observable o, Object arg) { this.sendMsg(arg.toString()); } }
-
测试类
public class TestAction { public static void main(String[] args) { // 定义消息发送器 Observer sms = new Sms(); Observer email = new Email(); Observer wechat = new Wechat(); // 定义商城,并依赖短信发送服务 Mall mall = new Mall(); mall.addObserver(sms); mall.addObserver(email); mall.addObserver(wechat); // 开始执行下单和支付 mall.order(); mall.buy(); } }
-
打印日志
【商城服务】用户下单 【微信服务】发送订单消息:用户支付成功 【邮箱服务】发送订单消息:用户支付成功 【短信服务】发送订单消息:用户支付成功 【商城服务】用户支付 【微信服务】发送订单消息:用户支付成功 【邮箱服务】发送订单消息:用户支付成功 【短信服务】发送订单消息:用户支付成功
以上代码使用了java提供的api也同样实现了观察者模式的功能。我们可以看看Observerable类中的源码,这里只截取了部分重要的代码。其中的实现原理与我们的示例二是一样的。
- Observerable部分源码
public class Observable {
private boolean changed = false;
// 定义观察者集合,可以供多个观察者订阅
private Vector<Observer> obs;
// 增加一个消息发送观察者
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* Deletes an observer from the set of observers of this object.
* Passing <CODE>null</CODE> to this method will have no effect.
* @param o the observer to be deleted.
*/
// 删除一个消息发送观察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
// 通知所有的消息观察者,开始发送消息
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
// 遍历所有观察者,一一通知
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
// 开关操作,要发送消息时先调用这里将开关打开
protected synchronized void setChanged() {
changed = true;
}
}
-
新的类图如下
优点
-
观察者与被观察者(发布者与订阅者)是抽象的耦合
耦合仅控制在抽象的接口或者顶级父类(ObserverAble)中,不会下沉到具体的实现类。避免了实际的耦合。对拓展开发有很好的支持
-
简单的触发机制
在被观察者这边来说可以随时随地的触发观察者机制。不用过多的关心观察者的具体实现。
缺点
- 虽然观察者模式比较容易实现,但不利于嵌套使用。如A观察者监听业务服务1的动作,同时又有一个B观察者要监听A观察者的业务动作后面可能还有C观察者等等。这个是程序无法控制的,且对于开发和维护上来说是很不方便的。所以在开发设计时需要考虑具体的业务场景,最好不要出现多层嵌套监听的情况。
- 在以上所述例子中所有的被观察者都是使用的同步的方式通知观察者的。但是在实际项目中观察者的数量可能会比较多,也有可能观察者处理触发事件效率比较低,这个时候如果使用同步的方式通知观察者,势必会导致被观察者(具体业务部分)的效率降低。当然这也是有解决方法的。如果业务允许观察者异步执行触发动作的话。我们可以使用异步的形式来通知观察者来执行动作,那么观察者这边可以使用缓存、消息队列等形式慢慢消化掉这些通知。
使用场景
文中就是一个比较典型的使用场景,属于跨系统的消息交换场景。还有就是观察者和被观察者两者的业务是需要可拆分联系不紧密的情况下。