前言
观察者模式也是对象行为模式的一种,又叫做发表-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、 咱们目前用的最多的就是各种MQ(Message Queue)都是基于这个模式的思想来实现的,生产者产生数据放到一个队列中,消费者观察生产者的消息队列的变化,从而接收消息,执行消费者本身的逻辑。
观察者模式
概念介绍
观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象。这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新。
这些观察者之间没有任何关联,可以根据业务需要增加删除观察者,易于系统扩展。
举例
还是来举实际的例子,来介绍设计模式,毕竟设计模式是一种抽象的东西,需要落到真正的实现中才能体现出它的价值。当我们在网上购物时,看到一件自己比较喜欢的商品,但是最近手头有点紧(已经开始吃土了),所以会先关注一下这个商品,一般的购物网站上都会有关注此商品这么一个功能的。为了就是当商品降价打折或是其他变化的时候能够通知到所有关注此商品的顾客。那么我们就以这个功能为例子来使用观察者模式实现一下。
抽象主题类
/** * 抽象被观察类 */ @Getter public abstract class Observable { //观察者集合,存储关注商品的所有顾客 protected List<Observer> observerList = Lists.newArrayList(); /** * 添加观察者(当一个顾客选择了关注商品时添加到观察者集合中) * @param observer 观察者 */ public void attach(Observer observer){ observerList.add(observer); } /** * 注销观察者(取消关注商品) * @param observer 观察者 */ public void detach(Observer observer){ observerList.remove(observer); } /** * 通知观察者的方法 */ public abstract void notice(Object obj); }
商品类
/** * 商品类 */ @Getter //lombok get方法 @AllArgsConstructor //lombok 以所有属性为参数的构造方法 @NoArgsConstructor //lombok 没有参数的构造方法 public class Product extends Observable { /** 商品名称 */ protected String name; /** 商品价格*/ protected BigDecimal price; /** * 商品名称变更 * @param name 商品名称 */ public void setName(String name){ this.name = name; //通知观察者 notice(name); } /** * 价格变更 * @param price 商品价格 */ public void setPrice(BigDecimal price){ this.price = price; //通知观察者 notice(price); } /** * 通知观察者的方法 */ @Override public void notice(Object obj) { if(Objects.nonNull(observerList)&&observerList.size()>0){ observerList.forEach((Observer observer) -> observer.update(obj)); } } }
抽象观察者类
/** * 抽象观察者 */ public abstract class Observer { /** * 更新 * @param obj 更新对象 */ public abstract void update(Object obj); }
名称观察者
/** * 名称观察者 */ public class NameObserver extends Observer { /** * 更新 * @param obj 更新对象 */ @Override public void update(Object obj) { if(obj instanceof String){ String name = (String) obj; System.out.println("您关注的商品名称发生了变化,最新的商品名称是"+name); } } }
价格观察者
/** * 价格观察者 */ public class PriceObserver extends Observer{ /** * 更新 * * @param obj 更新对象 */ @Override public void update(Object obj) { if(obj instanceof BigDecimal){ BigDecimal price = (BigDecimal)obj; System.out.println("您关注的商品价格发生了变化,最新的商品价格是:"+price); } } }
测试类
public class Test { public static void main(String[] args) { Product product = new Product("iphoneX",new BigDecimal(8999)); System.out.println("您关注的商品的名称是:"+product.getName()+",价格是:"+product.getPrice()); //创建观察者 NameObserver nameObserver = new NameObserver(); PriceObserver priceObserver = new PriceObserver(); //加入观察者 product.attach(nameObserver); product.attach(priceObserver); //产生变化,通知观察者 product.setName("iphoneX Max"); product.setPrice(new BigDecimal(12999)); } }
运行结果:
您关注的商品的名称是:iphoneX,价格是:8999
您关注的商品名称发生了变化,最新的商品名称是iphoneX Max
您关注的商品价格发生了变化,最新的商品价格是:12999
通过上面的运行结果我们就能看出来,当商品名称或价格发生变化时,会通知到相应的观察者,这就是观察者模式的具体应用了。那么通过例子我们也可以看出来观察者模式具体是由哪些角色组成的。
观察者模式的结构
观察者模式结构如下图
在观察者模式中存在如下几种角色:
抽象主题角色(Subject):抽象主题角色把所有的观察者对象的引用保存在一个列表里;每个主题都可以有任何数量的观察者。主题提供一个接口,可以加上或撤销观察者对象;主题角色又被称为被观察者角色。可以用抽象类或接口来实现。
抽象观察者角色(Observer):为所有的具体观察者定义一个接口,在得到通知时更新自己。抽象观察者角色通常是用一个抽象类或一个接口来实现;当然也可以用具体的类来实现。
具体主题角色(ConcreteSubject):具体主题保存对具体观察者对象有用的内部状态,在这种状态改变时,给其观察者发出一个具体的通知,具体主题角色又被称为具体被观察者角色。
具体观察者角色(ConcreteObserver):具体观察者角色用于保存一个指向具体主题对象的引用,和一个与主题的状态相符的状态。具体观察者角色实现抽象观察者角色所要求的更新自己的接口,以便使本身的状态与主题的状态对应。
总结
观察者模式是一种使用频率比较高的设计模式,凡是涉及到一对一或一对多的对象交互场景都可以使用观察者模式。
观察者模式的主要优点
1、观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当观察者角色。
2、观察者模式在观察目标和观察者之间建立一个抽象耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
3、观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
4、观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
观察者模式的主要缺点
1、如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
1、一个对象的改变将会导致一个或多个对象的改变,不清楚具体有多少对象以及这些被影响的对象是谁的情况。
2、如果有这样一个影响链的情况下也可以使用,例如A的改变会影响B,B的改变会影响C......,可以使用观察者模式设计一个链式触发机制。
想了解更多的设计模式请查看Java设计模式学习记录-GoF设计模式概述。
这个是我的个人公众号,文章以后也会同步到公众号上去,欢迎关注。