观察者模式(学习笔记)

  1. 意图

  定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,全部依赖于它的对象都获得通知并被自动更新java

  2. 动机    

  假设这样一种状况,顾客对某个特定品牌的产品很是感兴趣(例如最新型号的 iPhone 手机),而该产品很快将会在商店里出售。顾客能够天天来商店看看产品是否到货。但若是商品还没有到货时,绝大多数来到商店的顾客都会空手而归。另外一方面,每次新产品到货时,商店能够向全部顾客发送邮件(可能会被视为垃圾邮件)。这样,部分顾客就无需反复前往商店了,但也可能会惹恼对新产品没有兴趣的其余顾客。编辑器

  咱们彷佛遇到了一个矛盾:要么让顾客浪费时间检查产品是否到货,要么让商店浪费资源去通知没有需求的顾客。观察者模式能够解决这一问题。  ide

  观察者模式为发布者(将自身的状态改变通知给其余对象)类添加订阅机制,让每一个对象都能订阅或取消订阅发布者事件流。该机制包括:this

  1)一个用于存储订阅者(全部但愿关注发布者状态变化的其余对象)对象引用的列表成员变量;spa

  2)几个用于添加或删除该列表中订阅者的公有方法。日志

        

  这样,不管什么时候发生了重要的发布者事件,它都要遍历订阅者并调用其对象的特定通知方法。在实际应用中可能会有十几个不一样的订阅者类跟踪着同一个发布者类的事件, 咱们不但愿发布者与全部这些类相耦合的。所以,全部订阅者都必须实现一样的接口,发布者仅经过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。若是在应用中存在不一样类型的发布者,且但愿一个订阅者能够同时订阅多个发布者。须要让全部订阅者遵循相同的接口,并在该接口中描述几个订阅方法(须要将发布者做为参数传入方法中)便可。这样订阅者就能在不与具体发布者类耦合的状况下经过接口观察发布者的状态code

          

  3. 适用性

  • 一个抽象模型有两个方面,其中一个方面依赖于另外一方面。将这二者封装在独立的对象中,以使它们能够各自独立的改变和复用
  • 对一个对象地改变须要同时改变其它对象,而不知道具体有多少对象有待改变
  • 一个对象必须通知其余对象,而它又不能假定其余对象是谁。换言之,你不但愿这些对象是紧密耦合的

  4. 结构

         

  5. 效果

  Observer模式容许你独立地改变目标和观察者orm

  1. 目标和观察者间地抽象耦合   一个目标所知道的仅仅是它有一系列观察者,每一个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间地耦合是抽象和最小的。server

  2. 支持广播通讯    不像一般的请求,目标发送的通知不须要指定它的接收者。通知被自动广播给全部已向该目标对象登记的对象。另外,处理仍是忽略一个通知取决于观察者对象

  3. 意外的更新      因为一个观察者并不知道其余观察者地存在,它可能对改变目标的最终代价一无所知

  6. 代码实现    

  本例中,观察者模式在文本编辑器的对象之间创建了间接的合做关系。每当编辑器 (Editor)对象改变时,它都会通知其订阅者。 ​邮件通知监听器 (Email­Notification­Listener)和日志开启监听器 (Log­Open­Listener)都将经过执行其基本行为来对这些通知作出反应。
订阅者类不与编辑器类相耦合,且能在须要时在其余应用中复用。 ​编辑器类仅依赖于抽象订阅者接口。这样就能容许在不改变编辑器代码的状况下添加新的订阅者类型。

  publisher/EventManager.java: 基础发布者

package observer.publisher;

import observer.listeners.EventListener;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:01
 */
public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();

    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}

  editor/Editor.java: 具体发布者,由其余对象追踪

package observer.editor;

import observer.publisher.EventManager;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:01
 */
public class Editor {
    public EventManager events;
    private File file;

    public Editor() {
        this.events = new EventManager("open", "save");
    }

    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }

    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}

  listeners/EventListener.java: 通用观察者接口

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:02
 */
public interface EventListener {
    void update(String eventType, File file);
}

  listeners/EmailNotificationListener.java: 收到通知后发送邮件

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:02
 */
public class EmailNotificationListener implements EventListener{
    private String email;

    public EmailNotificationListener(String email) {
        this.email = email;
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}

  listeners/LogOpenListener.java: 收到通知后在日志中记录一条消息

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:03
 */
public class LogOpenListener implements EventListener{
    private File log;

    public LogOpenListener(String fileName) {
        this.log = new File(fileName);
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }

}

  Demo.java: 客户端代码

package observer;

import observer.editor.Editor;
import observer.listeners.EmailNotificationListener;
import observer.listeners.LogOpenListener;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:00
 */
public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
        editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));

        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  运行结果

Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt
Email to admin@example.com: Someone has performed save operation with the following file: test.txt

    

  7. 实现

  1)建立目标到其观察者之间的映射     一个目标对象跟踪它应通知的观察者的最简单的方法是显式地在目标中保存对它们地引用。然而,当目标不少而观察者较少时,这样存储可能代价过高。一个解决办法是用时间换空间,用一个关联查找机制(例如一个hash表)来维护目标到观察者地映射。这样一个没有观察者的目标就不产生存储开销。但另外一方面,这一方法增长了访问观察者的开销

  2)观察多个目标    有时候,一个观察者依赖于多个目标。例如,一个表格对象可能依赖于多个数据源。在这种状况下,必须扩展update接口,目标对象能够简单的将本身做为Update操做地一个参数,让观察者知道应该去检查哪一个目标

  3)谁触发更新     目标和它的观察者依赖于通知机制来保持一致。但到底哪一个对象调用Notify来触发更新? 这里有两个选择:

  • 由目标对象的状态设定操做在改变目标对象的状态后自动调用Notify。这种方法的优势是客户不须要记住要在目标对象上调用Notify,缺点是多个连续的操做会产生屡次连续的更新,可能效率较低
  • 让客户负责在合适的时候调用notify。这样作的优势是客户在一系列状态改变完成后一次性的触发更新,避免了没必要要的中间更新。缺点是给客户增长了触发更新的责任。因为客户可能会忘记调用Notify,这种方式交易出错

  4)在发出通知前确保目标的状态自身是一致的       在发出通知前确保状态自身一致这一点很重要,由于观察者在更新其状态的过程当中须要查询目标的当前状态。可使用模板方法发送通知来避免这种错误。定义那些子类能够重定义的原语操做,并将Notify做为模板方法中的最后一个操做,这样当子类重定义Subject的操做时,还能够保证该对象的状态是自身一致的。另外,最好在文档中注明哪一个Subject操做触发通知

  5)避免特定于观察者的更新协议——推/拉模型     观察者模式的实现常常须要让目标广播关于其改变的其余一些信息。目标将这些信息做为Update操做的一个参数传递出去。一个极端状况是,目标向观察者发送关于改变的详细信息,而无论它们须要与否,即推模型。另外一个极端是拉模型,目标除最小通知外什么也不送出,而在此以后由观察者显式的向目标询问细节。拉模型强调的是目标不知道它的观察者,而推模型假定目标知道一些观察者须要的信息。推模型使得观察者相对难以复用。另外一方面,拉模型效率会较差,由于观察者对象须要在没有目标对象的帮助下,肯定什么改变了

  6)显式地指定感兴趣的改变       能够经过扩展目标的注册接口,让观察者注册为仅对特定事件感兴趣的观察者。如上面例子中,将EventType做为参数传递给Notify 和Update方法

  7)封装复杂的更新语义        当目标和观察者间的依赖关系特别复杂时,可能须要一个维护这些关系的对象,即ChangeManager(更改管理器)。其目的是尽可能减小观察者反映其目标状态变化所需的工做量。例如,若是一个操做涉及几个相互依赖的目标进行改动,就必须保证在全部的目标更新完毕后,才一次性的通知它们的观察者,而不是每一个目标都通知观察者。ChangeManager是一个Mediator模式的实例。相比于,SimpleChangeManager,当一个观察者观察多个目标时,DAGChangeManager保证观察者仅接收一个更新

                   

  8. 与其余模式的关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不一样链接方式:
    责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
    命令在发送者和请求者之间创建单向链接
    中介者清除了发送者和请求者之间的直接链接,强制它们经过一个中介对象进行间接沟通
    观察者容许接收者动态地订阅或取消接收请求

  • 中介者和观察者有的时候会很是类似
    中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间创建动态的单向链接,使得部分对象可做为其余对象的附属发挥做用
    有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其余组件则做为订阅者,能够订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者很是类似

  9. 已知应用  

  观察者模式在 Java 代码中很常见,特别是在 GUI 组件中。它提供了在不与其余对象所属类耦合的状况下对其事件作出反应的方式  这里是核心 Java 程序库中该模式的一些示例:  java.util.Observer/ java.util.Observable (极少在真实世界中使用)  java.util.EventListener的全部实现 (几乎普遍存在于 Swing 组件中)  javax.servlet.http.HttpSessionBindingListener  javax.servlet.http.HttpSessionAttributeListener  javax.faces.event.PhaseListener  识别方法: 该模式能够经过将对象存储在列表中的订阅方法, 和对于面向该列表中对象的更新方法的调用来识别

相关文章
相关标签/搜索