您现在的位置是:首页 >技术杂谈 >设计模式之【观察者模式】,MQ的单机实现雏形网站首页技术杂谈

设计模式之【观察者模式】,MQ的单机实现雏形

秃了也弱了。 2024-06-17 11:28:08
简介设计模式之【观察者模式】,MQ的单机实现雏形

一、什么是观察者模式

观察者模式(Observer Pattern),又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并被自动更新。属于行为型模式。

观察者模式的核心是将观察者与被观察者解耦,以类似于消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。

1、观察者模式应用场景

在软件系统中,当系统一方行为依赖于另一方行为的变动时,可以使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。观察者模式适用于以下几种应用场景:

  • 当一个抽象模型包含两个方面的内容,其中一个方面依赖于另一个方面;
  • 其他一个或多个对象的变化依赖于另一个对象的变化;
  • 实现类似广播机制的功能,无需知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播;
  • 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

2、观察者模式的四大角色

在这里插入图片描述

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

3、观察者模式优缺点

优点:

  • 观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则;
  • 分离了表示层(观察者)和数据逻辑层(被观察者),并建立了一套触发机制,使得数据的变化可以响应到多个表示层上;
  • 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。

缺点:

  • 如果观察者数量过多,则事件通知会耗时较长;
  • 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件;
  • 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。

4、观察者模式和中介模式的区别

观察者模式有多种实现方式。虽然经典的实现方式没法彻底解耦观察者和被观察者,观察者需要注册到被观察者中,被观察者状态更新需要调用观察者的 update() 方法。但是,在跨进程的实现方式中,我们可以利用消息队列实现彻底解耦,观察者和被观察者都只需要跟消息队列交互,观察者完全不知道被观察者的存在,被观察者也完全不知道观察者的存在。

中介模式也是为了解耦对象之间的交互,所有的参与者都只与中介进行交互。而观察者模式中的消息队列,就有点类似中介模式中的“中介”,观察者模式的中观察者和被观察者,就有点类似中介模式中的“参与者”。

在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。

而中介模式正好相反。只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类,通过先后调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。

二、实例

1、观察者模式的一般写法

//抽象观察者
public interface IObserver<E> {
    void update(E event);
}
//具体观察者
public class ConcreteObserver<E> implements IObserver<E> {
    public void update(E event) {
        System.out.println("receive event: " + event);
    }
}
//抽象主题者
public interface ISubject<E> {
    boolean attach(IObserver<E> observer);

    boolean detach(IObserver<E> observer);

    void notify(E event);
}
//具体主题者
public class ConcreteSubject<E> implements ISubject<E> {
    private List<IObserver<E>> observers = new ArrayList<IObserver<E>>();

    public boolean attach(IObserver<E> observer) {
        return !this.observers.contains(observer) && this.observers.add(observer);
    }

    public boolean detach(IObserver<E> observer) {
        return this.observers.remove(observer);
    }

    public void notify(E event) {
        for (IObserver<E> observer : this.observers) {
            observer.update(event);
        }
    }
}
public class Test {

    public static void main(String[] args) {
        // 被观察者
        ISubject<String> observable = new ConcreteSubject<String>();
        // 观察者
        IObserver<String> observer = new ConcreteObserver<String>();
        // 注册
        observable.attach(observer);
        // 通知
        observable.notify("hello");
    }

}

2、微信公众号案例

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

类图如下:
在这里插入图片描述

// 定义抽象观察者类,里面定义一个更新的方法
public interface Observer {
	void update(String message);
}
// 定义具体观察者类,微信用户是观察者,里面实现了更新的方法
public class WeixinUser implements Observer {
	// 微信用户名
	private String name;
	public WeixinUser(String name) {
		this.name = name;
	}
	@Override
	public void update(String message) {
		System.out.println(name + "-" + message);
	}
}
// 定义抽象主题类,提供了attach、detach、notify三个方法
public interface Subject {
	//增加订阅者
	public void attach(Observer observer);
	//删除订阅者
	public void detach(Observer observer);
	//通知订阅者更新消息
	public void notify(String message);
}
// 微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
public class SubscriptionSubject implements Subject {
	//储存订阅公众号的微信用户
	private List<Observer> weixinUserlist = new ArrayList<Observer>();
	@Override
	public void attach(Observer observer) {
		weixinUserlist.add(observer);
	}
	@Override
	public void detach(Observer observer) {
		weixinUserlist.remove(observer);
	}
	@Override
	public void notify(String message) {
		for (Observer observer : weixinUserlist) {
			observer.update(message);
		}
	}
}
// 客户端程序
public class Client {
	public static void main(String[] args) {
		SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
		//创建微信用户
		WeixinUser user1=new WeixinUser("孙悟空");
		WeixinUser user2=new WeixinUser("猪悟能");
		WeixinUser user3=new WeixinUser("沙悟净");
		//订阅公众号
		mSubscriptionSubject.attach(user1);
		mSubscriptionSubject.attach(user2);
		mSubscriptionSubject.attach(user3);
		//公众号更新发出消息给订阅的微信用户
		mSubscriptionSubject.notify("秃了也弱了的专栏更新了");
	}
}

3、鼠标响应事件API案例

做过安卓或者是桌面程序的小伙伴会对这很熟悉,鼠标点击事件也都是通过观察者模式来实现的,我们自己用代码来实现一下。

/**
 * 观察者抽象
 */
public interface EventListener {

}
import java.lang.reflect.Method;
// 事件
public class Event {
    //事件源,动作是由谁发出的
    private Object source;
    //事件触发,要通知谁(观察者)
    private EventListener target;
    //观察者给的回应
    private Method callback;
    //事件的名称
    private String trigger;
    //事件的触发事件
    private long time;

    public Event(EventListener target, Method callback) {
        this.target = target;
        this.callback = callback;
    }

    public Object getSource() {
        return source;
    }

    public Event setSource(Object source) {
        this.source = source;
        return this;
    }

    public String getTrigger() {
        return trigger;
    }

    public Event setTrigger(String trigger) {
        this.trigger = trigger;
        return this;
    }

    public long getTime() {
        return time;
    }

    public Event setTime(long time) {
        this.time = time;
        return this;
    }

    public Method getCallback() {
        return callback;
    }

    public EventListener getTarget() {
        return target;
    }

    @Override
    public String toString() {
        return "Event{" +
                "source=" + source +
                ", target=" + target +
                ", callback=" + callback +
                ", trigger='" + trigger + ''' +
                ", time=" + time +
                '}';
    }
}

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
 * 被观察者的抽象
 */
public class EventContext {
    protected Map<String,Event> events = new HashMap<String,Event>();

    public void addLisenter(String eventType, EventListener target, Method callback){
        events.put(eventType,new Event(target,callback));
    }

    public void addLisenter(String eventType, EventListener target){
        try {
            this.addLisenter(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
        }catch (NoSuchMethodException e){
            return;
        }
    }

    private String toUpperFirstCase(String eventType) {
        char [] chars = eventType.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

    private void trigger(Event event){
        event.setSource(this);
        event.setTime(System.currentTimeMillis());

        try {
            if (event.getCallback() != null) {
                //用反射调用回调函数
                event.getCallback().invoke(event.getTarget(), event);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected void trigger(String trigger){
        if(!this.events.containsKey(trigger)){return;}
        trigger(this.events.get(trigger).setTrigger(trigger));
    }
}

// 观察类型
public interface MouseEventType {
    String ON_CLICK = "click";

    String ON_MOVE = "move";
}

/**
 * 具体的被观察者
 */
public class Mouse extends EventContext {
    public void click(){
        System.out.println("调用单击方法");
        this.trigger(MouseEventType.ON_CLICK);
    }

    public void move(){
        System.out.println("调用移动方法");
        this.trigger(MouseEventType.ON_MOVE);
    }
}

/**
 * 观察者
 */
public class MouseEventLisenter implements EventListener {

    public void onClick(Event e){
        System.out.println("==========触发鼠标单击事件========
" + e);
    }

    public void onMove(Event e){

        System.out.println("==========触发鼠标移动事件========
" + e);
    }
}

// 客户端代码
public class Test {
    public static void main(String[] args) {
        MouseEventLisenter lisenter = new MouseEventLisenter();

        Mouse mouse = new Mouse();
        mouse.addLisenter(MouseEventType.ON_CLICK,lisenter);
        mouse.addLisenter(MouseEventType.ON_MOVE,lisenter);

        mouse.click();
        mouse.move();
    }
}

三、实现一个异步非阻塞框架

我们前面介绍过,观察者模式的一个弊端就是需要一个个通知,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件。

1、EventBus

EventBus 翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus 就是一个比较著名的 EventBus 框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。

2、使用MQ

使用MQ可以应用于分布式场景的发布订阅模型。

四、源码中的观察者模式

1、Observable/Observer

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

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

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

我们实现一个警察抓小偷的案例,警察是观察者,小偷是被观察者。代码如下:

// 小偷是一个被观察者,所以需要继承Observable类
public class Thief extends Observable {
	private String name;
	public Thief(String name) {
		this.name = name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void steal() {
		System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
		super.setChanged(); //changed = true
		super.notifyObservers();
	}
}
// 警察是一个观察者,所以需要让其实现Observer接口
public class Policemen implements Observer {
	private String name;
	public Policemen(String name) {
		this.name = name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	@Override
	public void update(Observable o, Object arg) {
		System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
	}
}
// 客户端代码
public class Client {
	public static void main(String[] args) {
		//创建小偷对象
		Thief t = new Thief("隔壁老王");
		//创建警察对象
		Policemen p = new Policemen("小李");
		//让警察盯着小偷
		t.addObserver(p);
		//小偷偷东西
		t.steal();
	}
}

2、Flow API

但是Observable和Observer在jdk1.9之后被弃用了。取而代之的是java.util.concurrent.Flow 类:
响应式编程详解,带你熟悉Reactor响应式编程

3、Spring中的Event

Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。