设计模式之——观察者模式

1 观察者模式的定义

观察者模式:Observer Pattern,也叫发布-订阅模式(Publish-Subscribe Pattern)。具体定义是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的通用类图:

设计模式之——观察者模式_第1张图片
观察者模式通用类图

  • Subject被观察者
    定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。一般是抽象类或者接口,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
  • ConcreteSubject具体的被观察者
    定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
  • Observer观察者
    观察者接收到消息后,就进行update操作,对接收到的信息进行处理。
  • ConcreteObserver具体的观察者
    每个观察者在接收到消息后的处理应该是不同的,每个观察者有自己独特的处理逻辑。

2 观察者模式通用代码示例

  1. 被观察者
  • 抽象的被观察者
@Slf4j
public abstract class Subject {
    /**
     * 定义一个观察者数组
     */
    private Vector observers = new Vector<>();

    /**
     * 增加一个观察者
     *
     * @return true:增加成功;false:增加失败
     */
    public boolean addObserver(Observer observer) {
        return this.observers.add(observer);
    }

    /**
     * 删除一个观察者
     *
     * @return true:删除成功;false:删除失败
     */
    public boolean removeObserver(Observer observer) {
        return this.observers.remove(observer);
    }

    /**
     * 通知所有的观察者
     */
    public void notifyAllObservers() {
        log.info("{}通知所有的观察者.", this.getClass().getSimpleName());
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
  • 具体被观察者
@Slf4j
public class ConcreteSubject extends Subject {

    /**
     * 具体被观察者独特的业务逻辑
     */
    public void uniqueMethod() {
        //具体业务
        log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
        super.notifyAllObservers();
    }
}
  1. 观察者
  • 观察者接口
    观察者一般是一个接口。
public interface Observer {
    /**
     * 观察者的操作
     */
    public void update();
}
  • 具体观察者
    每一个实现该接口的实现类都是具体观察者。
@Slf4j
public class ConcreteObserver implements Observer {
    /**
     * 实现具体的观察者接口,具有独特的业务
     */
    @Override
    public void update() {
        log.info("观察者{}接收到消息,正在进行处理...", this.getClass().getSimpleName());
    }
}
  1. 场景类
@Slf4j
public class Client {
    public static void main(String[] args) {
        //创建一个被观察者
        ConcreteSubject subject = new ConcreteSubject();
        //定义一个观察者
        Observer observer = new ConcreteObserver();
        //观察者观察被观察者
        subject.addObserver(observer);
        //被观察者进行活动,观察者进行观察者
        subject.uniqueMethod();
    }
}

3 观察者模式的优缺点

3.1 优点

  1. 观察者和被观察者之间是抽象耦合
    观察者和被观察者之间通过抽象耦合,这样设计在增加观察者或被观察者的时候,都非常容易扩展,不修改原有代码只需要增加具体实现就可以进行扩展,符合“开闭原则”。
  2. 建立一套触发机制
    观察者模式可以更方便地实现具体业务中的复杂逻辑关系,形成逻辑链条。

3.2 缺点

  1. 开发效率低
    一个被观察者、多个观察者,开发和调试都会比较复杂,在大型项目中影响开发效率。
  2. 执行效率低
    在Java中消息的通知默认是顺序执行,一个观察者出错,会影响整体的执行效率,因此一般可以考虑采用异步方式。尤其是在多级触发的时候,执行效率更会受影响。
    因此在设计时注意考虑影响。

4 观察者模式的应用

4.1 应用场景

  • 关联行为场景
    关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景
  • 跨系统的消息交换场景
    如消息队列的处理机制。

4.2 使用观察者模式的注意事项

使用观察者模式有两个重点问题需要解决。

  1. 广播链问题
    观察者模式中的观察者同时也可以是被观察者,因此很容易形成逻辑链。逻辑链一旦建立,逻辑就比较复杂,可维护性非常差。
    根据经验建议:
    在一个观察者模式中,最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),还是比较好控制的。

广播链责任链模式最大的区别是:

  • 观察者广播链:消息在传播的过程中是随时更改的,它是由相邻的两个节点协商的消息结构。
  • 责任链模式:消息在传递的过程中基本上保持不变。如果要改变,也只有在原有的消息上进行修正。
  1. 异步处理问题
    如果被观察者发生动作了,观察者要做出响应。如果观察者比较多,就要用异步处理。异步处理就需要考虑线程安全和队列的问题,详情可以参考JDK的Message Queue。

5 观察者模式的扩展

5.1 Java中的观察者模式

Java本身支持了观察者模式,并且提供了可用于扩展的父类:java.util.Observable实现类和java.util.Observer接口。

设计模式之——观察者模式_第2张图片
Java中观察者模式通用类图

  1. 被观察者
    被观察者,继承java.util.Observable:
@Slf4j
public class ConcreteSubject extends Observable {
    /**
     * 具体被观察者独特的业务逻辑
     */
    public void uniqueMethod() {
        //具体业务
        log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
    }

    /**
     * 业务逻辑
     */
    public void doSomething() {
        log.info("被观察者{}执行业务逻辑...", this.getClass().getSimpleName());
        this.setChanged();
        this.notifyObservers();
    }
}
  1. 观察者
    观察者实现java.util.Observer接口:
@Slf4j
public class ConcreteObserver implements Observer {
    /**
     * 观察者名字
     */
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        log.info("观察者[{}]观察到变化。{}.update", this.name, this.getClass().getSimpleName());
        ConcreteSubject observer= (ConcreteSubject) o;
        observer.uniqueMethod();
    }
}
  1. 场景类
    场景应用观察者和被观察者
@Slf4j
public class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer1 = new ConcreteObserver("OBSERVER1");
        ConcreteObserver observer2 = new ConcreteObserver("OBSERVER2");
        subject.addObserver(observer1);
        subject.addObserver(observer2);

        subject.doSomething();
    }
}

运行结果:

23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 被观察者ConcreteSubject执行业务逻辑...
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER2]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER1]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod

5.2 订阅-发布模型

EJB3(Enterprise JavaBean)是一个非常优秀的框架,比起前两代,还算轻量级。EJB3中有3个类型的Bean:

  • Session Bean
  • Entity Bean
  • MessageDriven Bean。
    MessageDriven Bean(MDB),消息驱动Bean:消息的发布者(Provider)发布一个消息(一个消息驱动Bean),通过EJB容器(一般是Message Queue消息队列)通知订阅者做出相应。原理上很简单,类似于观察者模式的升级版。

6 项目中真实的观察者模式

一般在真实的项目中,系统设计时会对观察者模式进行改造或改装,主要体现在以下3个方面。

  1. 观察者和被观察者之间的消息沟通
    被观察者状态改变会触发观察者的一个行为,同时传递一个消息给观察者。在实际中一般做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,数据传输对象)。DTO一般是一个纯粹的JavaBean,由被观察者生成,由观察者消费。如果考虑到远程传输,可以通过JSON或XML格式传递。
  2. 观察者响应方式
    观察者一般逻辑复杂,需要接受被观察者传递过来的信息并进行处理。当一个观察者——多个被观察者时,性能就需要进行考虑。如果观察者来不及响应,被观察者执行时间也会被拉长。这种情况有两种处理办法:
  • 异步架构
    采用多线程技术,被观察者或者观察者都可以启动线程,可以明显地提高系统性能。
  • 同步架构
    采用缓存技术,保证有足够的资源,随时响应。但是,代价是开发难度大,需要进行充分的压力测试。
  1. 被观察者尽量自己做主
    被观察者具体实现根据业务逻辑决定是否要通知观察者,而不是消息到达观察者时才判断是否需要消费,导致浪费网络资源,加重消费者的处理逻辑和处理压力。

参考

  1. 设计模式之禅

你可能感兴趣的:(设计模式之——观察者模式)