程序员潇然 发表于 2022-8-2 15:57:14

观察者Observer发布订阅模式源监听行为型设计模式(二十三)

观察者模式 Observer

!(data/attachment/forum/202208/02/154912ulvxa919paovzdzx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

### 意图

定义对象一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都得到通知并自动更新。

别名:依赖(Dependents),发布订阅(Publish-Subscribe)源-监听(Source-Listener)

!(data/attachment/forum/202208/02/154926chz5uuhaqh4a5f24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

《Hold On, We're Going Home》是加拿大说唱歌手德雷克与制作组合Majid Jordan合作的节奏布鲁斯歌曲

第一句“I got my eyes on you”就是“我一直关注你”

I got my eyes on you,

You're everything that I see

I want your hot love and emotion, endlessly

I can't get over you,

You left your mark on me......

电视剧中,也是经常出现”观察、监视“,比如《黎明之前》

是刘江执导2010年出品的谍战剧,由吴秀波、林永健、陆剑民、海清等领衔主演。豆瓣评分高达9.2。

有一段周汉亭被盯梢的桥段,有负责门口监视的,有负责电话汇报情况的,有负责指挥的....他的一举一动都在敌人的监视之内,引发无数个探子连锁行动。

歌词中因为喜欢妹子所以持续关注,妹子是目标,歌手是观察者。

电视剧中敌人为了破坏我党工作,所以监视,周汉亭是目标,众多探子是观察者。

通过对目标的观察,观察者可以获得事物的动向情况,进而做出进一步的行动。这就是观察。

在程序中,也经常会出现这种场景

一个系统中必然由多个相互协作的类组成,很常见的问题就是维持状态的一致性或者说联动效果。

观察者模式就是为了解决这种问题,处理“协作者”之间的一致性与联动问题。

比如,数据库中对某个字段进行了更新,可能需要同步修改其他字段

比如,执行一个main方法,IDE的窗口的联动效果,如下图所示,点击run执行后

底部状态栏会显示Build状态,很快完成后就开始运行,侧边栏显示运行状态,然后控制台打印输出信息

这是一系列的联动效果

!(data/attachment/forum/202208/02/154944yskzd12hhw4mdgh6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

比如,同一份数据有多种图表展示,如果数据发生变化,每个图表都需要发生变化

!(data/attachment/forum/202208/02/155011fuq5szzz7w4ipu3u.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

### 结构

假设目标为Subject ,观察者为Observer(一个或者多个)

最简单的实现方式,就是Subject直接调用Observer的方法。

!(data/attachment/forum/202208/02/155021nhb1khko81nxob7d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

当事件发生后,直接调用Observer的相关方法。

伪代码如下

Subject内使用List维护观察者

当事件发生,也就是方法f()中,循环通知观察者

省略了观察者的维护工作,也就是添加和删除

```java
class Subject{
    List<Observer> observerList = new ArrayList<>();
    void f(){
//do sth...
      for(Observer o:observerList){
//调用相关方法
            o.doSthElse();
      }
    }
```

依赖倒置原则中,要求应该面向抽象进行编程,而不是面向细节。

上面的结构中,不管是目标还是观察者的扩展都不方便,所以抽象提取。

!(data/attachment/forum/202208/02/155052snewee3nwbuquxen.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

这就是观察者模式的基本结构。

**抽象观察者角色Observer**

为所有的具体的观察者定义一个接口,得到主题的通知信息后,进行同步响应。

一般包含一个方法叫做update()用以同步响应

**抽象主题角色Subject**

主题角色把所有观察者对象保存在集合中,提供管理工作,添加和删除

并且,提供通知工作,也就是调用相关观察者的update

**具体主题角色ConcreteSubject**

实现抽象主题接口协议,当状态方式发生变化时,对观察者进行通知

**具体观察者角色ConcreteObserver**

实现抽象观察者定义的接口,完成自身相关的同步更新活动

### 代码示例

抽象观察者角色,提供统一的更新方法

```java
package observer;
public interface Observer {
void update();
}
```

抽象的Subject,内部使用List<Observer>保存观察者对象

提供了attach和dettach方法用于添加和删除观察者

并且提供了通知方法,就是遍历调用Observer

```java
package observer;
import java.util.LinkedList;
import java.util.List;

public abstract class Subject {

    List<Observer> observerList;
   
    Subject() {
      observerList = new LinkedList<>();
    }
   
    void attach(Observer o) {
      observerList.add(o);
    }
   
    void dettach(Observer o) {
      observerList.remove(o);
    }
   
    void notifyObservers() {
      for (Observer o : observerList) {
            o.update();
      }
    }
}
```

具体的主题角色,他有一个行动方法,行动后通知其他的观察者

观察者在父类中列表里面定义

```java
package observer;
public class ConcreteSubject extends Subject {
    public void action() {
      System.out.println("下雨了");
      super.notifyObservers();
    }
}
```

具体的观察者,实现具体的行动

```java
package observer;
public class ConcreateObserver implements Observer {
    @Override
    public void update() {
      System.out.println("那我收衣服了");
    }
}
```

测试代码

```java
package observer;
public class Test {
public static void main(String[] args) {
      Observer o1 = new ConcreateObserver();
      ConcreteSubject subject = new ConcreteSubject();
      subject.attach(o1);
      subject.action();
    }
}
```

!(data/attachment/forum/202208/02/155219vqq8hqb70da7bdq0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

如果新增加一个观察者

```java
package observer;
public class ConcreteObserver1 implements Observer {
    @Override
    public void update() {
      System.out.println("那我得把窗户关上");
    }
}
```

测试代码

!(data/attachment/forum/202208/02/155248n75drl7e6wfbuyvn.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

如上图所示,有两个观察者(比如张三和李四),当包租婆大喊一声下雨了之后,他们都得到通知

张三去收衣服了,李四把自己的窗户关上了

以上就是观察者模式的简单示例。

观察者模式的核心在于对于观察者的管理和维护,以及发送通知。

消息的发布订阅,在程序中就是消息发布者调用订阅者的相关方法

观察者模式将发布者与订阅者进行解耦,不再是直接的方法调用,通过引入Observer角色,完成了发布者与具体订阅者之间的解耦

也是一种形式的“方法调用”的解耦

### Java的观察者模式

!(data/attachment/forum/202208/02/155306bnffr5fqe4ip1r1i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

观察者接口Observer
是一个interface,定义了一个update方法
void update(Observable o, Object arg);
接受两个参数,一个是被观察对象,一个是参数

**被观察角色Observerable** 等同于前文Subject

内部使用Vector维护观察者Observer

提供了对观察者的管理相关方法,添加、删除、计算个数、删除、删除所有等

Observerable内部使用标志位记录是否已经发生变化

private boolean changed = false;
发生变动后,设置为true,通知后设置为false

!(data/attachment/forum/202208/02/155320z4im65tf8kksdyy6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


| addObserver(Observer o)                      | **添加**指定观察者                                             |
| ---------------------------------------------- | ---------------------------------------------------------------- |
| deleteObserver(Observer o)                   | **删除**指定观察者                                             |
| deleteObservers()                            | **删除所有**观察者                                             |
| countObservers                               | 返回**观察者个数**                                             |
| notifyObservers()notifyObservers(Object arg) | **通知所有观察者**一个无参,一个有参参数传递给Observer的update |

Observerable的实现原理很简单:

* 使用Vector保存观察者,提供了添加、删除、删除全部的方法,并且可以返回观察者的个数。
* 如果的确发生变动,将会通知所有的观察者

提供了两个版本的notifyObservers,一个有参,有个无参,参数对应update方法的第二个参数
!(data/attachment/forum/202208/02/155522zputswbpcshsp8ww.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

通知后,将会清除变动状态

!(data/attachment/forum/202208/02/155538crrrxm3d7rrxmbxm.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
Observable中Vector<Observer>是私有的,也并没有提供访问器,只是可以添加、删除、或者清除所有

所以子类并不知道具体的Observer

!(data/attachment/forum/202208/02/155555squ8h5uqsrrfpvv2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

代码示例

```java
package observer.java;
import java.util.Observable;
public class Subject extends Observable {
    public void changeState() {
      System.out.println("下雨了........");
      setChanged();
      notifyObservers();
    }
}
```


```java
package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher1 implements Observer {
    @Override
    public void update(Observable o, Object arg) {
      System.out.println("被观察者:" + o + " ,参数 " + arg + " ,观察者1");
    }
}
```


```java
package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher2 implements Observer {
    @Override
    public void update(Observable o, Object arg) {
      System.out.println("被观察者:" + o + " ,参数 " + arg + " ,观察者2");
    }
}
```

!(data/attachment/forum/202208/02/155648a8vcdqci5sye2k4e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")



Subject 继承Observable类,自定义了changeState()方法

在方法中,调用

setChanged();

notifyObservers();
完成状态改变和通知。

从打印信息可以看得出来

update方法接收到的第一个参数,就是我们的被观察者对象

第二个参数可以用来封装传递信息

所以在java中,除非场景特殊,你又不需要自己写观察者模式了,已经内置了

通过继承和实现相应的类和接口即可。

GOF的设计模式出版于95,JDK 1.0始于1996,所以,Java天然支持某些设计模式也很正常

而且,设计模式是经验总结,GOF将他们归纳总结使之广为人知,但是并不代表这些经验史无前例

JDK的开发者人家本身就有这些“经验”也不足为奇。

### 与中介者模式区别

**观察者模式**用于**一对多**依赖场景中的解耦,通过引入Observer角色,将消息发布者与具体的订阅者进行解耦

**中介者模式**是将系统内部多对多的复杂耦合关系,借助于中介者进行解耦,将网状结构,简化为星型结构,将**多对多转换为一对多**

他们**都能够实现消息发布者与接收者的解耦,消息发布者都不知道具体的消息接收者**

发起消息的Colleague同事类角色是被观察者,中介类Mediator是观察者,调用其他的同事类进行协作

**尽管观察者模式强调“一致性通信”**

**中介者模式强调“内部组件协作”**

**但是根本还是方法执行时,需要同步调用其他对象**

两个模式之间是一种类似的关系,在有些场景可替代转换。

如果协作关系比较简单,可以实现为一对多的形式,使用观察者模式

如果协作关系更加复杂,那么就可以使用中介者模式

### 总结

观察者模式是在一对多的依赖场景中,对消息发布者和消息订阅者的解耦

在观察者和被观察者之间建立了一个抽象的耦合,而不是强关联

通过引入观察者角色,发布者不依赖具体的观察者,从而Subject和Observer可以独立发展

被观察者仅仅知道有N个观察者,他们以集合的形式被管理,都是Observer角色,但是完全不知道具体的观察者

观察者模式的支持广播,被观察者会向所有的观察者发送消息。

增加新的观察者时,不需要修改客户端代码,只需要扩展Observer接口即可,满足开闭原则。

一个观察者,也可能是一个被观察者,如果出现观察者调用链,将会发生多米诺骨牌效应不断地进行调用,后果可能是难以预知的。

所以要谨慎识别双重身份的对象,也就是即是观察者也是被观察者的对象。

被观察者不知道具体的观察者,也更不可能知道观察者具体的行为

当发生状态变化时,被观察者一个小举动,可能引起很大的性能消耗,而被观察者对此毫不知情,可能仍旧以为云淡风轻。

当一个对象状态的改变,需要同时改变其他对象时,可以考虑观察者模式

当一个对象必须通知其他人时,但是他又不知道到底是谁时,可以考虑观察者模式

或者将一个抽象模型中的两个关联部分解耦,以便独立发展,提高复用性,解耦不表示断开,仍旧需要联系,可以借助于观察者模式进行联系

总之

观察模式通过引入观察者角色,将调用者与被调用者解耦,通过观察者角色联系。

但凡类似“广播”“发布订阅”的场景,都可以考虑是否可用。

!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-122-1-1.html `
页: [1]
查看完整版本: 观察者Observer发布订阅模式源监听行为型设计模式(二十三)