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

本文详细介绍了观察者模式的概念、应用场景、角色以及优缺点,并通过微信公众号和鼠标响应事件API举例说明其实现。此外,还探讨了如何使用EventBus构建异步非阻塞框架,并分析了Java中Observable/Observer以及SpringEvent的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


全网最全最细的【设计模式】总目录,收藏起来慢慢啃,看完不懂砍我

一、什么是观察者模式

观察者模式(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("==========触发鼠标单击事件========\n" + e);
    }

    public void onMove(Event e){

        System.out.println("==========触发鼠标移动事件========\n" + 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事件管理

### 观察者设计模式概念 观察者设计模式是一种软件设计模式,在该模式下,对象之间存在一对多的关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新[^1]。 这种模式特别适用于构建事件处理系统或发布-订阅机制。通过这种方式,可以有效地解耦组件之间的关系,使得系统的各个部分更加独立,易于维护和发展[^3]。 ### 实现方式 #### 定义接口 为了实现观察者模式,通常先定义两个核心接口:`Subject`(主题) 和 `Observer`(观察者)[^4]。其中: - **Subject 接口** 负责管理观察者的注册、移除以及通知操作; - **Observer 接口** 则提供了一个回调方法用于接收来自 Subject 的消息。 ```cpp // C++ Example of Observer Pattern Interfaces class Subject { public: virtual void registerObserver(Observer* o) = 0; virtual void removeObserver(Observer* o) = 0; virtual void notifyObservers() const = 0; }; class Observer { public: virtual void update(const std::string& message) = 0; }; ``` #### 主题与观察者的具体实现 接下来就是创建具体的主题类和多个不同类型的观察者类来继承上述抽象基类,并完成各自的功能逻辑。 例如,在游戏开发中,可以有一个玩家作为主体(Player),而聊天通知器(ChatNotifier)作为一个特定类型的观察者监听玩家的动作变化并向其他玩家发送即时通讯信息[^2]。 ```cpp // Concrete Implementation in Game Context class Player : public Subject { private: std::vector<Observer*> observers_; int health_; public: void registerObserver(Observer* o) override { /* ... */ } void removeObserver(Observer* o) override { /* ... */ } void notifyObservers() const override { /* ... */ } void setHealth(int newHealth) { if (health_ != newHealth) { health_ = newHealth; notifyObservers(); // Notify all registered Observers about the change. } } }; class ChatNotifier : public Observer { public: void update(const std::string& msg) override { // Send chat notification to other players... } }; ``` ### 应用场景 观察者模式广泛应用于各种领域,尤其是在需要动态响应数据变动的情况下非常有用。比如在图形界面编程里用来同步UI控件状态;在网络通信框架中建立异步的消息传递机制;或是像提到的例子那样,在分布式环境中利用消息队列(MQ)实施跨服务的通知体系结构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值