06 观察者与行为型模式

Kaleido Lv4

1 观察者模式

1.1 模式动机

  • 建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。
    1. 发生改变的对象称为观察目标
    2. 被通知的对象称为观察者
  • 一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

1.2 模式定义

  • 观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
  • 观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。

1.3 模式结构

  • 一对多的单向依赖关系
  1. 在通常情况下,我们并不严格区分抽象类和抽象接口,但观察者模式明确指出subject是抽象类而observer是抽象接口,这是为什么?
    ConcreteObserver除了Observer的职责以外,可能还有其他的类型职责,如1.4模式分析中WeatherData的例子,CurrentConditionDisplay除了观察更新的职责以外,还有display的职责,而这两者的职责是正交的,不适合放在一个类中实现。

如何判断是使用抽象类还是抽象接口?看类的实际职责和其在模式中的职责是否正交。

  1. 从图中我们可以看出ConcreteObserver其实持有了subject,那这还是一对多的单向依赖关系吗?
    ConcreteObserver持有subject的主要作用是用于detach,而在detach的过程中,其实并没有使用到subject的数据,所以我们不认为这是一种依赖。

1.4 模式分析

1.4.1 回到 WeatherData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface Subject {    
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

public interface Observer {
public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
public void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;

public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}

public void display() {
System.out.println("Current conditions:" + temperature + "F degrees and " + humidity + "% humidity");
}
}
  1. 现在需求发生变更,如果新增了需要进行update的信息,应该如何处理?
    将所有需要进行update的信息封装为一个类,这样就不需要去更改update接口。

  2. 每个天气板可能只需要展示部分数据,但update会传递所有数据,应该如何处理?
    使用拉模式,由观察者调用get方法,确定需要获取哪些数据。

1.4.2 松耦合

  • 当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
  • 观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
  • 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,就可以自由地改变他们。

1.5 模式优缺点

1.5.1 观察者模式的优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式符合开闭原则的要求。

1.5.2 观察者模式的缺点

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

1.6 模式适用环境

  1. 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  2. 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  3. 一个对象必须通知其他对象,而并不知道这些对象是谁。
  4. 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……,可以使用观察者模式创建一种链式触发机制。

1.7 模式应用

  1. JDK1.1 版本及以后的各个版本中,事件处理模型采用基于观察者模式的委派事件模型(Delegation Event Model, DEM)。
    • 在 DEM 中,事件的发布者称为事件源(Event Source),而订阅者叫做事件监听器(Event Listener),在这个过程中还可以通过事件对象(Event Object)来传递与事件相关的信息,可以在事件监听者的实现类中实现事件处理,因此事件监听对象又可以称为事件处理对象。
    • 事件源对象、事件监听对象(事件处理对象)和事件对象构成了 Java 事件处理模型的三要素。
  2. 除了 AWT 中的事件处理之外,Java 语言解析 XML 的技术 SAX2 以及 Servlet 技术的事件处理机制都基于 DEM,它们都是观察者模式的应用。
  3. 观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

1.8 模式扩展

1.8.1 Java 语言提供的对观察者模式的支持

![[Pasted image 20240402113942.png]]

  • Observer 接口:void update(Observable o, Object arg);
    为什么需要传递Observable o对象?因为Observer可能观察了多个Observable对象,需要确定arg是哪个Observable的。
  • Observable 类
    1. Observable()
    2. addObserver(Observer o)
    3. deleteObserver (Observer o)
    4. notifyObservers()、notifyObservers(Object arg)
    5. deleteObservers
    6. setChanged()
    7. clearChanged()
    8. hasChanged()
    9. countObservers()
      ![[Pasted image 20240402114454.png]]
      两种notifyObservers方法分别对应推和拉两种模式。后一种方法即拉模式,传递给观察者this参数,由观察者调用get方法,确定需要获取哪些数据。

1.8.2 谁触发谁更新

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

  1. Push 更新:由目标对象的状态设定操作在改变目标对象的状态后自动调用 Notify 。这种方法的优点是客户不需要记住要在目标对象上调用 Notify ,缺点是多个连续的操作会产生多次连续的更新,可能效率较低。
  2. Pull 更新:让客户负责在适当的时候调用 Notify 。这样做的优点是客户可以在一系列的状态改变完成后再一次性地触发更新,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。由于客户可能会忘记调用 Notify ,这种方式较易出错。

1.8.3 封装复杂的更新语义

  • 当目标和观察者间的依赖关系特别复杂时 , 可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器 Change Manager。
  • 它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如 , 如果一个操作涉及到对几个相互依赖的目标进行改动 , 就必须保证仅在所有目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。
    ![[Pasted image 20240402115228.png]]

1.8.4 MVC 模式

MVC 模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现 MVC 模式,观察者模式中的观察目标就是 MVC 模式中的模型(Model),而观察者就是 MVC 中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。
![[Pasted image 20240402115354.png]]

1.9 观察者模式小结

  1. 观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。观察者模式是一种对象行为型模式。
  2. 观察者模式包含四个角色:
    1. 目标又称为主题,它是指被观察的对象;
    2. 具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;
    3. 观察者将对观察目标的改变做出反应;
    4. 在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
  3. 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
  4. 观察者模式:
    1. 主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;
    2. 其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  5. 观察者模式适用情况包括:
    1. 一个抽象模型有两个方面,其中一个方面依赖于另一个方面;
    2. 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;
    3. 一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。
  6. 在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 Java 语言对观察者模式的支持。

2 模板方法模式

2.1 模式动机

  • 模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
  • 在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。

2.2 模式定义

模板方法模式(Template Method Pattern):定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种类行为型模式。

2.3 模式结构

![[Pasted image 20240409102233.png]]

2.4 模式分析

  • 模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系。
  • 在模板方法模式的使用过程中,要求开发抽象类和开发具体子类的设计师之间进行协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。实现这些具体逻辑步骤的方法称为基本方法(Primitive Method),而将这些基本法方法汇总起来的方法称为模板方法(Template Method),模板方法模式的名字从此而来。
    • 模板方法:一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。
    • 基本方法:基本方法是实现算法各个步骤的方法,是模板方法的组成部分。
      • 抽象方法(Abstract Method)
      • 具体方法(Concrete Method)
      • 钩子方法(Hook Method):“挂钩”方法和空方法

继承复用 VS 合成复用

  • Title: 06 观察者与行为型模式
  • Author: Kaleido
  • Created at : 2024-06-20 11:27:05
  • Updated at : 2024-06-20 11:37:16
  • Link: https://redefine.ohevan.com/2024/06/20/2024-spring-软件系统设计-06/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments