设计模式-观察者模式及应用

在现实生活中,任何对象事务都不是孤立的。往往一个对象的改变,可能会造成相关对象的连锁反应。比如说我们日常生活中,参与公共交通的过程。红灯行绿灯行是灌输在每个人的意识中,当路口的红绿灯(对象)是红色时,所有参与这个路口的机动车、行人(对象)都会暂定,等待绿灯的指示的到来。
类似于这种案例生活中还有很多,比如我们订阅了某个微信公众号,当公众号主体推送一篇公众号推文。所有的订阅者这时候都会收到此篇文章。

程序的设计中,我们也有类似的模式。这就是接下来要说的观察者模式的使用。

定义及结构特点

观察者(Observer)模式的定义:观察者模式也称发布订阅模式(Publish-Subscribe Design Pattern) 在GoF的《设计模式》⼀书中,它的定义是, 在对象之间定义⼀个⼀对多的依赖,当⼀个对象状态改变的时候,所有依赖的对象都会⾃动收到通知。

观察者模式结构:

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式结构图:
在这里插入图片描述

案例代码1:

通过模板对代码,更能直观体会设计模式的使用

  • 抽象主题
/**
 * 
 * @author jerry.feng
 * 抽象目标
 */
public abstract class Subject {
	protected List<Observer> observers = new ArrayList<Observer>();
	
    //增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }
    //删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    //通知观察者方法
    public abstract void notifyObserver();

}
  • 具体目标
//被观察者
public class ConcreteSubject extends Subject{

	@Override
	public void notifyObserver() {
       //遍历给所有的观察者发送消息
		for (Observer observer : observers) {
			observer.receiveResponse();
		}
	}
}
  • 抽象观察者
//抽象观察者接口
public interface Observer {
	//消息接收响应
	void receiveResponse();
}
  • 具体观察者1
public class ConcreteObsever1 implements Observer {

	@Override
	public void receiveResponse() {
		System.out.println("观察者1-收到消息响应");
	}
}
  • 具体观察者2
public class ConcreteObsever2 implements Observer {

	@Override
	public void receiveResponse() {
		System.out.println("观察者2-收到消息响应");
	}
}
  • 使用方
public class Client {
	public static void main(String[] args) {
		Subject concreteSubject = new ConcreteSubject();
		//观察者1
		Observer concreteObsever1 = new ConcreteObsever1();
		//观察者2
		Observer concreteObsever2 = new ConcreteObsever2();
		//添加观察者
		concreteSubject.add(concreteObsever1);
		concreteSubject.add(concreteObsever2);
		// 时间发生 通知所有观察者
		concreteSubject.notifyObserver();
	}
}

执行结果:

观察者1-收到消息响应
观察者2-收到消息响应

通过本案例可以实现简单观察者模式,在被监听者即目标对象发生改变时,通过遍历当前目标对象中维护的观察者列表,给每一个观察者发送消息。

我们不仅可以自己实现观察者模式,JDK中也为我们提供了相关的工具类, java.util.Observable 类(抽象目标)和 java.util.Observer(抽象观察者) 接口定义了观察者模式,我们只需简单实现接口,即可实现案例中的观察者模式代码,下面通过案例2的代码来感受一下,JDK提供的工具包是如何使用。

案例代码2:
  • 具体目标对象
// 被观察者角色  实现Observable 接口
public class ObservableObj extends Observable{
	@Override
	public void notifyObservers(Object arg) {
		System.out.println("观察者数量:"+this.countObservers());
		//内部状态是否发生改变
		setChanged();
		super.notifyObservers(arg);
	}
}
  • 观察者1
public class Observer1 implements Observer{

	@Override
	public void update(Observable o, Object arg) {
		System.out.println("观察者1收到信息:"+arg.toString());
	}
}
  • 观察者2
public class Observer2 implements Observer{

	@Override
	public void update(Observable o, Object arg) {
		System.out.println("观察者2收到信息:"+arg);
		
	}
}
  • 使用方
public class Client {
	public static void main(String[] args) {
	    Observer observer1 = new Observer1();
	    Observer observer2 = new Observer2();
	    
	    Observable observableObj = new ObservableObj();
	    observableObj.addObserver(observer1);
	    observableObj.addObserver(observer2);
	    observableObj.notifyObservers("msg1");
	}
}

执行结果:

观察者数量:2
观察者2收到信息:msg
观察者1收到信息:msg

案例2代码结构:

在这里插入图片描述

通过本案例,发现JDK给我提供的观察者模式的工具类,也能方便的实现观察者模式,目标对象java.util.Observable中,提供了
增加删除观察者的方法
重要方法:

  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
  • void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时, notifyObservers() 才会通知观察者。

不妨看下JDK源码是如何实现,观察者的维护,及状态位的标识维护

private boolean changed = false;
    private Vector<Observer> obs;
    
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {  
        // 检验状态位
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    
    protected synchronized void setChanged() {
        changed = true;
    }

内部通过Vector<Observer> 维护观察者,这相比于ArrayList来说是线程安全的,而且每个增加删除的方法都有synchronized关键字,这就保证了在多线程场景下的安全性。
且状态位的维护,也是同步的方式,向观察者通知时,会校验当前状态位的状态。整个过程都是同步的方式,看完整套代码,是不是觉得除了增加安全性的考虑,JDK实现的这套代码也不难。

问题思考与拓展

观察者模式很场景,主要的作用就是解耦。往大的说整个设计模式不管是创建型、结构性、还是行为型。其宗旨就是解耦,只是站的角度不同。 观察者模式常用于我们的功能设计,如订阅发布消息、邮件的发送等,还有代码层面,架构层面的解耦也会用到。

上面的案例都是同步阻塞的方式,加入业务系统中使用到同步通知的这种方式,遍历各监听者发送通知,监听者做完业务操作,势必会影响我们的整体性能。这时候可以通过异步处理的方式,在监听者部分处理,这里也会有弊端频繁的创建回收线程,也是不小的开销。

市面上也有好的观察者模式的框架,我们可以如果考虑性能方面原因,想设计一套基于架构层面更完善的发布订阅模式。Google Guava 框架中的事件总线EventBus是个不错的选择。

EventBus提供了同步阻塞、和异步非阻塞的API实现方式。下面通过案例实现一个业务层面的代码。

案例代码3:

这里多个了个比前两个案例多个事件的概念其实一样,和JDK中提供的API的消息(事件)是一个意思,这里案例实现这样一个小demo发送不同的事件类型消息这里模拟微信朋友圈、公众号两种消息的发送,来让不同的监听者监听,其思想还是观察者模式。

  • 事件中心 提供不同事件总线API
//事件中心
public class EventBusSource {

    //同步事件总线
    public static final EventBus eventBus = new EventBus();

    //异步事件总线
    public static final EventBus asyncEventBus = new AsyncEventBus(Executors.newFixedThreadPool(4));

    public static EventBus getEventBus() {
        return eventBus;
    }

    public static EventBus getAsyncEventBus() {
        return asyncEventBus;
    }
}
  • 目标对象(被观察者)
 * @description 被观察者
 * @since
 */
public class Subject {
// 获取同步事件总线
    EventBus eventBus = EventBusSource.getEventBus();

    public void addEventListener(List<EventListener> eventListeners) {
        // 向事件总线注册监听者(观察者)
        for (EventListener eventListener : eventListeners) {
            eventBus.register(eventListener);
        }
    }

    /**
     * 消息发送
     *
     * @param domainEvent
     */
    public void notifyMsg(DomainEvent domainEvent) {
        eventBus.post(domainEvent);
    }
}
  • 事件抽象(消息)
public abstract class DomainEvent extends EventObject {

    // 消息
    private Object msg;

    public DomainEvent(Object msg) {
        super(msg);
        this.msg = msg;

    }

    public Object getMsg() {
        return msg;
    }

}
  • 具体事件类型1 (朋友圈事件)
public class FriendGroupEvent extends DomainEvent{

    public FriendGroupEvent(Object msg) {
        super(msg);
    }
}
  • 具体事件类型2 (公众号事件)
public class GongzhonghaoEvent extends DomainEvent{

    public GongzhonghaoEvent(Object msg) {
        super(msg);
    }
}
  • 监听者抽象(观察者)
public interface EventListener {

}
  • 具体观察者1 监听公众号消息
public class WechatEventListener1 implements EventListener {

    @Subscribe
    public void on(GongzhonghaoEvent event) {
        System.out.println("监听者1,收到了公众推送:" + event.getMsg());
    }
}
  • 具体观察者2 监听公众号消息
public class WechatEventListener2 implements EventListener {

    @Subscribe
    public void on(GongzhonghaoEvent event){
        System.out.println("监听者2,收到了公众推送:" + event.getMsg());
    }
}
  • 具体观察者3 监听朋友圈消息
public class AEventListener implements EventListener {

    @Subscribe
    public void on(FriendGroupEvent friendGroupEvent) {
        System.out.println("小A收到朋友圈一条新动态:" + friendGroupEvent.getMsg());
    }
}
  • 使用方
public class Client {

    public static void main(String[] args) {
        //监听者
        EventListener wechatEventListener1 = new WechatEventListener1();
        EventListener wechatEventListener2 = new WechatEventListener2();
        EventListener aEventListener = new AEventListener();

        Subject subject = new Subject();
        // 添加监听者
        subject.addEventListener(Arrays.asList(wechatEventListener1,wechatEventListener2,aEventListener));

        //发送公众号消息
        GongzhonghaoEvent event = new GongzhonghaoEvent("第一篇个人公众号");
        subject.notifyMsg(event);
        //发送朋友圈消息
        FriendGroupEvent friendGroupEvent = new FriendGroupEvent("第一条朋友圈");
        subject.notifyMsg(friendGroupEvent);
    }
}

执行结果:

监听者1,收到了公众推送:第一篇个人公众号
监听者2,收到了公众推送:第一篇个人公众号
小A收到朋友圈一条新动态:第一条朋友圈

不同监听者通过不同消息类型的订阅,更加灵活的实现消息订阅通知,比我们传统的方式实现更灵活;监听者只需要在业务方法上使用注解@Subscribe,方法参数指定具体的事件类型即可。而且可以通过异步非阻塞API的接口,实现异步事件通知。

观察者模式优缺点

优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则;
  • 在一定程度上满足“开闭原则”,增加观察者,无需修改目标对象中维护观察者这部分逻辑;
  • 一对多的设计模式,简化了此类问题的设计难度。

缺点:

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用;
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率(文中说了解决方案)。
观察者模式使用场景及源码使用
  • 使用场景
  1. 适用于系统中,需要将事件的触发与响应解耦;一对多的这种对应关系,一个对象的改变,其他相关的对象都需要做出相应的响应;
  2. 架构层面的解耦合。
  • 源码
    spring事件机制,之前有写过一篇这样的文章,可以参考来看
    https://blog.youkuaiyun.com/fw19940314/article/details/100010397)

✨✨ 欢迎🔔订阅个人的微信公众号 享及时博文更新
个人工作号

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值