首页 > 其他分享 >【设计模式】观察者模式Observer:消息的订阅-发布

【设计模式】观察者模式Observer:消息的订阅-发布

时间:2023-09-11 20:33:53浏览次数:41  
标签:Observer 对象 void 观察者 模式 Message 设计模式 public

(目录)


观察者模式是一种非常流行的设计模式,也常被叫作订阅-发布模式。 观察者模式在现代的软件开发中应用非常广泛,比如,商品系统、物流系统、监控系统、运营数据分析系统等。 常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。 当观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

image.png


原理

原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

这个定义中包含了两个前提条件:

  1. 被依赖的对象叫作被观察者,依赖的对象叫作观察者;
  2. 观察者观察被观察者的状态变化。

不过,这听上去还是有点太抽象,更容易理解下面这些不同的对于观察者模式的叫法:

  1. 发布者-订阅者;
  2. 生产者-消费者;
  3. 事件发布-事件监听。

不管怎么叫,这些模式在本质上都是观察者模式

观察者模式 UML 图如下:

image-20230911194219998

从该 UML 图中,观察者模式包含的四个关键角色:

  • 发布者(Publisher):也被叫作主题、被订阅者、被观察者等,通常是指观察者关心的相关对象集合,比如,将 GitLab 上的 Git 库作为发布者,我们关心 Git 库的代码发生了变更后做特定的操作;
  • 具体发布者(PublisherImpl):实现了发布者定义的方法的具体实现类。;
  • 订阅者(Observer):也叫作观察者,它会存储一个注册列表,用于存放订阅者。当发布者发布消息或事件时,会通知到订阅者进行处理;
  • 具体订阅者(ObserverImpl):实现具体定义的方法操作。

UML 对应的代码实现,是一种经典的实现方式,如下:

public interface IPublisher {
    void addObserver(IObserver o);
    void removeObserver(IObserver o);
    void notify(double amt);
}

public interface IObserver {
    void notify(String acct, double amt);
}

public class ObserverImpl implements IObserver {
    @Override
    public void notify(String acct, double amt) {
        System.out.println("=== 接收到通知:账户:"+acct + " 账单:"+amt);
    }
}

public class PublisherImpl implements IPublisher {

    private String acct;
    private double balance;
    private List<IObserver> myObservers;

    public PublisherImpl(String anAcct, double aBalance){
        this.acct = anAcct;
        this.balance = aBalance;
        this.myObservers = new ArrayList<>();
    }

    @Override
    public void addObserver(IObserver o) {
        myObservers.add(o);
    }

    @Override
    public void removeObserver(IObserver o) {
        myObservers.remove(o);
    }

    @Override
    public void notify(double amt) {
        balance -= amt;
        if (balance < 0) {
            overdrawn();
        }
    }

    private void overdrawn() {
        for (IObserver o : myObservers) {
            o.notify(acct, balance);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        IPublisher account = new PublisherImpl("TEST123", 10.00);
        IObserver bill = new ObserverImpl();
        account.addObserver(bill);
        account.notify(11.00);
    }
}

// 输出结果
// === 接收到通知:账户:TEST123 账单:-1.0

使用场景

观察者模式常见的使用场景有以下几种:

  1. **当一个对象状态的改变需要改变其他对象时。**比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
  2. **一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。**比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
  3. **需要创建一种链式触发机制时。**比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
  4. **微博或微信朋友圈发送的场景。**这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
  5. **需要建立基于事件触发的场景。**比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

代码实现 消息订阅功能

实现一个简单的消息订阅通知的功能

首先,定义观察者 IMessageObserver。它只有一个 update 方法,当有消息 Message 发送过来时就会调用该方法:

public interface IMessageObserver {
    void update(Message m);
}

然后,定义一个被观察者 ISubject ,它定义了三个方法

  • 增加观察者方法 attach;
  • 删除观察者方法 detach ;
  • 更新通知方法 notifyUpdate:
public interface ISubject {

    /* 增加观察者 */
    void attach(IMessageObserver o);
    /* 删除观察者 */
    void detach(IMessageObserver o);
    /* 更新通知 */
    void notifyUpdate(Message m);
    
}

接着,定义消息的具体数据结构 Message,这里使用一个 content 消息内容:

public class Message {
    final String content;

    public Message (String m) {
        this.content = m;
    }
    public String getContent() {
        return content;
    }
    
}

再接下来,实现 ISubject 的具体发布者类 MessagePublisher,它持有一组观察者 MessageObserver 实例,可以通过 attach 和 detach 接口方法新增和删除观察者:

public class MessagePublisher implements ISubject {

    private List<IMessageObserver> observers = new ArrayList<>();

    @Override
    public void attach(IMessageObserver o) {
        observers.add(o);
    }

    @Override
    public void detach(IMessageObserver o) {
        observers.remove(o);
    }

    @Override
    public void notifyUpdate(Message m) {
        observers.forEach(x->x.update(m));
    }

}

最后,实现三个具体订阅者类,它们都实现了 MessageObserver 接口,分别代表不同的消息接收后的对应处理操作:

public class MessageSubscriber1 implements IMessageObserver{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber1 :: " + m.getContent());
    }
}

public class MessageSubscriber2 implements IMessageObserver{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber2 :: " + m.getContent());
    }
}

public class MessageSubscriber3 implements IMessageObserver{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber3 :: " + m.getContent());
    }
}

运行一段单元测试,具体运行结果:

public class Client {
    public static void main(String[] args) {
        IMessageObserver s1 = new MessageSubscriber1();
        IMessageObserver s2 = new MessageSubscriber2();
        IMessageObserver s3 = new MessageSubscriber3();
        ISubject p = new MessagePublisher();
        p.attach(s1);
        p.attach(s2);
        /* s1 和 s2 会收到消息通知 */
        p.notifyUpdate(new Message("First Message"));
        p.detach(s1);
        p.attach(s3);
        /* s2 和 s3 会收到消息通知 */
        p.notifyUpdate(new Message("Second Message"));
    }
}
// 输出结果
// MessageSubscriber1 :: First Message
// MessageSubscriber2 :: First Message
// MessageSubscriber2 :: Second Message
// MessageSubscriber3 :: Second Message

观察者模式使用场景的特点在于找到合适的被观察者,定义一个通知列表,将需要通知的对象放到这个通知列表中,当被观察者需要发起通知时,就会通知这个列表中的所有“人”。


使用观察者模式的理由

使用观察者模式的原因,主要有以下两个:

  1. 为了方便捕获观察对象的变化并及时做出相应的操作。 观察者模式对于对象自身属性或行为发生变化后,需要快速应对的应用场景尤其有效,比如:

​ 购物包裹的运送。因为商品变成包裹的形式后会出现各种各样的状态变化(配送中、接收中、退货中等),这些状态变化非常快,并且每一个状态都会对应一系列连锁的操作,这时使用观察者模式就非常方便,能够通知需要知道这些状态变化的系统,并做相应的处理,比如,物流监控、性能监控、运营系统等;

  1. 为了提升代码扩展性。 如果不使用观察者模式来捕获一个被观察对象的属性变化,那么就需要在被观察对象执行代码逻辑中加入调用通知某个对象进行变更的逻辑,这样不仅增加了代码的耦合性,也让代码扩展变得非常困难,因为要想新增一个新的观察对象,就需要修改被观察对象的代码,这样的扩展性非常低。相反,使用观察者模式则只需要将观察者对象注册到被观察者存储的观察者列表中就能完成代码扩展。

优缺点

观察者模式主要有以下三个优点:

  1. 降低系统与系统之间的耦合性。 比如,建立以事件驱动的系统,能创建更多观察者与被观察者,对象之间相互的关系比较清晰,可以随时独立添加或删除观察者;
  2. 提升代码扩展性。 由于观察者和被观察之间是抽象耦合,所以增删具体类并不影响抽象类之间的关系,这样不仅满足开闭原则,也满足里氏替换原则,能够很好地提升代码扩展性;
  3. 可以建立一套基于目标对象特定操作或数据的触发机制。 比如,基于消息的通知系统、性能监控系统、用户数据跟踪系统等,观察者通过特定的事件触发或捕获特定的操作来完成一系列的操作。

观察者模式的缺点如下:

  1. **增加代码的理解难度。 **由于使用组合关系,被观察者和观察者之间属于松散关系,所以在代码逻辑的理解上,就需要提前搞清楚上下文中有哪些中间类或调用逻辑关系,这样才能正确理解逻辑;
  2. **降低了系统性能。 **观察者模式通常需要事件触发,当观察者对象越多时,被观察者需要通知观察者所花费的时间也会越长,这样会在某种程度上影响程序的效率。

总结

观察者模式在原理上是一个比较抽象的模式,但是业界根据不同的应用场景和需求总结出了很多不同的实现方式,这让观察者模式变得易于使用。 在观察者模式中,被观察者通常会维护一个观察者列表。当被观察者的状态发生改变时,就会通知观察者。 观察者模式现在被大量应用在分布式系统的开发中。 从本质上来讲,观察者模式描述了如何建立对象与对象之间一种简单的依赖关系。 在现实中,最为常见的观察者模式的例子就是订阅微信公众号,当公众号发布一篇新文章时,能在微信上收到消息,然后选择合适的时间打开阅读。 因此,在使用观察者模式时,要重点关注哪些变化是需要从被观察对象那里捕获(观察到)并进行下一步处理的,一方面不能盲目捕获所有的变化,另一方面也不能遗漏重要的变化。换句话说,找到合适的变化并进行正确的处理才是使用观察者模式的正确打开方式。

标签:Observer,对象,void,观察者,模式,Message,设计模式,public
From: https://blog.51cto.com/panyujie/7438142

相关文章

  • 5.前端设计模式之容器/展现模式
    Enforceseparationofconcernsbyseparatingtheviewfromtheapplicationlogic通过将视图层和应用逻辑分离实现关注点分离 这个有点像Java应用开发中经常看到的MVC架构模式,实现数据、业务逻辑和展示层分离。这个模式在React中需要两个组件实现:容器组件主要负责获取数据,获取......
  • 【设计模式】备忘录模式Memento - 在聊天会话中记录历史消息
    (目录)相较于其他设计模式,备忘录模式不算太常用,但好在这个模式理解、掌握起来并不难,代码实现也比较简单,应用场景更是比较明确和有限。一般应用于编辑器或会话上下文中防丢失、撤销、恢复等场景中。模式原理分析备忘录模式的原始定义是:捕获并外部化对象的内部状态,以便以后可......
  • 设计模式--禅
    设计模式视频一、六大设计原则1、单一职责一个方法尽可能做一件事情单一职责原则(SingleResponsibilityPrinciple,SRP)2、接口隔离原则尽量依赖最小的接口六大设计模式原则-接口隔离原则3、依赖倒置原则:高层模块不应该依赖低层模块(基础模块),两者都应该依赖其抽象,传递的是抽象抽象不应......
  • 4. 前端设计模式之原型模式
    PrototypePattern原型模式:在多个对象间共享相同的属性  JavaScript原生支持原型链也是实现继承的基础,如以下代码虽然是使用的ES2016新的语法classc创建的类Dog,然后又使用new实例化对象dog1、dog2、dog3,底层依然操作的原型链:classDog{constructor(name){this.name......
  • 设计模式-中介者模式
    中介者模式定义+用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示地互相引用,从而使耦合松散,并且可以独立地改变他们的交互。UML类图使用场景+一般用于一组对象以定义良好但复杂的方式进行通信的场景,比如窗体的Form或者web页面asp+想定制一个分......
  • 趣解设计模式之《办理入职这么难吗?》
    〇、小故事小王大学毕业,经过大学期间的刻苦学习,终于成功的面试上了一家公司。按照约定的时间,小王兴高采烈的来到公司前台去办理入职手续。“您好,我是来办理入职手续的”小王对前台妹妹说,前台妹妹看都没看他一眼,顺手递给了他一个单子,带着一丝不耐烦的语气跟他说,“照着单子做就行”......
  • 适配器设计模式解决接口冲突
    title:适配器设计模式解决接口冲突index_img:https://tuchuangs.com/imgs/2023/08/04/f341f43b9362c8a1.pngtags:-JavaSE-接口categories:-JavaSEhide:falseexcerpt:适配器设计模式解决接口冲突问题描述当一个接口中抽象方法过多,但是我只要使用其中一部......
  • 【设计模式】命令模式Command:在一次请求中封装多个参数
    (目录)命令模式使用频率不算太高。如果熟悉函数式编程的话,会发现命令模式完全没有使用的必要,甚至在业务开发的场景中也很少使用到。不过对于想要找到正确抽象的设计者来说,命令模式的设计思想却非常值得借鉴。命令和查询的区别:查询,获取一个不可变的结果;命令,改变状态,不一定获......
  • java设计模式,简单工厂和抽象工厂有什么区别?
    java设计模式,简单工厂和抽象工厂有什么区别?简单工厂模式:这个模式本身很简单而且使用在业务较简单的情况下。一般用于小项目或者具体产品很少扩展的情况(这样工厂类才不用经常更改)。它由三种角色组成:工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工......
  • 设计模式-单例模式
    保证在整个软件系统中,对某个类只能存在一个对象实例。饿汉式(类加载时创建,没用到也创建)1、构造器私有化(防止new对象)。2、类内部创建私有的静态对象。3、用一个公共的getInstance()静态方法返回该对象。如Runtime类懒汉式(使用才创建)1、仍然使构造器私有化。2、类内部定义静......