Java DesignPartten Code

设计模式-观察者模式

Posted on 2020-10-04,6 min read

定义

  • Define a one-to-many dependency between objects so that when one object changes state,all its
    dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每
    当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)
  • 组成:
    • Subject被观察者:定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者
    • Observer观察者:观察者接收到消息后,即进行update操作,对接收到的信息进行处理
    • ConcreteSubject具体的被观察者:定义被观察者自己的业务逻辑,同时定义对那些事件进行通知
    • ConcreteObserver具体的观察者
  • 通用类图
观察者模式通用类图

优点

  • 观察者和被观察者之间是抽象耦合的:不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象级定义,在系统扩展方面更是得心应手
  • 建立一套触发机制:如在一个优选平台对接了淘宝、京东等各个电商平台。如果某个电商平台更新了新的商品就可以触发优选平台对库存进行更新。

缺点

  • 观察者模式要考虑一下开发效率和运行效率:一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行的,一个观察者卡壳,会影响整体的执行效率。所以一般考虑使用异步的方式。

使用场景

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

代码

观察者模式

注意:在Java中默认提供了观察者模式的接口,观察者为Observable,是一个实现类。被观察者是Observer,是一个接口。在实际使用的时候只需要继承和实现这两个就行了。

此处为了演示,使用了自定义的接口

本项目使用类图

代码

被观察者抽象(抽象类)[对应Java util包下的Observable]

package com.pattern.observer;

import java.util.ArrayList;
import java.util.List;

public abstract class Subject {
    /**
     * 观察者的集合类,用于存储所有要通知的观察者。单线程环境下可以使用Arraylist,多线程可以使用CopyOnWriteArraylist之类的集合类
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 添加一个观察者
     * @param observer 观察者
     */
    public void addObserver(Observer observer){
        this.observers.add(observer);
    }

    /**
     * 删除一个观察者
     * @param observer 观察者
     */
    public void deleteObserver(Observer observer){
        this.observers.remove(observer);
    }

    /**
     * 通知所有的观察者
     */
    public void notifyObserver(){
        for (Observer observer: observers){
            observer.update();
        }
    }

}

具体的被观察者(继承自被观察者抽象

package com.pattern.observer;

public class ConcreteSubject extends Subject {
    public void doSomething(){
        System.out.println("被观察者完成了自己的业务逻辑");
        // 通知所有观察者
        super.notifyObserver();
    }
}

观察者接口(接口)[相当于Java util包下的 Observer]

package com.pattern.observer;

/**
 * 观察者
 */
public interface Observer {
    /**
     * 更新方法,当被观察者有被观察的操作后通知观察者更新
     */
     void update();
}

具体的观察者(实现观察者接口

package com.pattern.observer;


import java.util.Observable;

public class ConcreteObserver implements Observer {

    @Override
    public void update() {
        System.out.println("观察者接受到信息,并进行处理");
    }
}

测试代码

package com.pattern;

import com.pattern.observer.ConcreteObserver;
import com.pattern.observer.ConcreteSubject;
import com.pattern.observer.Observer;
import org.junit.Test;

public class ObserverTest {
    @Test
    public void test(){
        // 创建一个被观察者
        ConcreteSubject subject = new ConcreteSubject();
        // 定义一个观察者
        Observer observer = new ConcreteObserver();
        // 添加观察者
        subject.addObserver(observer);
        // 开始活动
        subject.doSomething();
    }
}

结果

被观察者完成了自己的业务逻辑
观察者接受到信息,并进行处理

Process finished with exit code 0

注意

  • 在Java实际开发中不推荐自己定义接口,只用继承Observable和实现Observer就行了

  • 广播链问题:类似数据库中的触发器链问题(表A写了一个触发器,内容是一个字段更新后更新表B的一条数据,而表B也有个触发器,更新表C......),一个观察者可以有双重身份,既是观察者也是被观察者。但是如果这个链一旦形成逻辑就比较复杂,可维护性非常差。建议消息最多转发一次。

  • 异步处理问题:如果在通知观察者的时候,一个观察者处理时间比较长,或者是远程调用网络有时延。那么处理时间就会比较长。所以会使用异步处理,那么就要注意同步了。推荐看看Message Queue

  • 项目中的观察者模式:在实际项目中通常会对观察者模式进行改造

    • 观察者和被观察者之间的消息沟通:被观察者状态改变会触发观察者的一个行为,同时会传递一个消息给观察者。在项目中的一般做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(数据传输对象)。DTO由被观察者生成,观察者消费
    • 观察者响应方式:如果一个观察者多个被观察者的情况下(如:同时监控淘宝、京东的商品)。如果观察者来不及响应,被观察者的执行效率也会降低。通常有两种解决办法:
      • 采用多线程技术
      • 使用缓存技术
    • 被观察者尽量自己做主:被观察者的状态改变不一定要通知观察者。使用在设计的时候要灵活考虑。如上面代码中被观察者的doSomething方法可以改成doSomething(boolean isNotifyObserver)来设置是否通知观察者。以减少不必要的开销。

下一篇: 一个模拟Cache的并发优化的案例→

loading...