跳至主要內容

Observer 模式

KSJ大约 5 分钟设计模式设计模式TypeScript管理状态

为什么使用观察者模式

观察者模式非常适合用于需要自动更新的场景。例如,在图形用户界面(GUI)应用程序中,当数据模型发生变化时,所有显示该数据的视图都需要自动更新。通过使用观察者模式,我们可以将这些视图注册为观察者,当数据模型发生变化时,它们会自动收到通知并更新显示。

示例代码

在这个示例中,我们展示了如何使用观察者模式来管理状态变化。观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。以下是 TypeScript 实现的代码示例:

// DigitObserver.ts
import { NumberGenerator } from './numberGenerator';
import { Observer } from './observer';
export class DigitObserver implements Observer {
  update(number: NumberGenerator): void {
    console.log(`DigitObserver: ${number.getNumber()}`);
  }
}

// graphObserver.ts
import { Observer } from './observer';
import { NumberGenerator } from './numberGenerator';
export class GraphObserver implements Observer {
  update(generator: NumberGenerator): void {
    const count = generator.getNumber();
    let graph = '';
    for (let i = 0; i < count; i++) {
      graph += '*';
    }
    console.log(`GraphObserver: \n${graph}\n ${generator.getNumber()}\n${graph} `);
  }
}

// main.ts
import { NumberGenerator } from './numberGenerator';
import { Observer } from './observer';
import { RandomNumberGenerator } from './randomNumberGenerator';
import { DigitObserver } from './DigitObserver';
import { GraphObserver } from './graphObserver';
const generator: NumberGenerator = new RandomNumberGenerator();
const observer1 = new DigitObserver();
const observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();

// numberGenerator.ts
import { Observer } from './observer';
export abstract class NumberGenerator {
  private observers: Observer[] = [];
  public addObserver(observer: Observer): void {
    this.observers.push(observer);
  }
  public deleteObserver(observer: Observer): void {
    this.observers = this.observers.filter((o) => o !== observer);
  }
  public notifyObservers(): void {
    this.observers.forEach((o) => o.update(this));
  }
  public abstract getNumber(): number;
  public abstract execute(): void;
}

// observer.ts
import {NumberGenerator} from './numberGenerator';
export interface Observer {
  update(generator: NumberGenerator): void;
}

// randomNumberGenerator.ts
import {NumberGenerator} from './numberGenerator';
export class RandomNumberGenerator extends NumberGenerator {
  private number: number;
  constructor() {
    super();
    this.number = 0;
  }
  getNumber(): number {
    return this.number;
  }
  execute(): void {
    for (let i = 0; i < 20; i++) {
      this.number = Math.floor(Math.random() * 50);
      this.notifyObservers();
    }
  }
}

运行结果

PS design_patern> ts-node "d:\code\design_patern\src\observer\main.ts"
DigitObserver: 13
GraphObserver:
*************
 13
*************
DigitObserver: 43
GraphObserver:
*******************************************
 43
*******************************************
DigitObserver: 46
GraphObserver:
**********************************************
 46
**********************************************
DigitObserver: 18
GraphObserver:
******************
 18
******************
DigitObserver: 20
GraphObserver:
********************
 20
********************
DigitObserver: 49
GraphObserver:
*************************************************
 49
*************************************************
DigitObserver: 25
GraphObserver:
*************************
 25
*************************
DigitObserver: 26
GraphObserver:
**************************
 26
**************************
DigitObserver: 37
GraphObserver:
*************************************
 37
*************************************
DigitObserver: 2
GraphObserver:
**
 2
**
DigitObserver: 10
GraphObserver:
**********
 10
**********
DigitObserver: 0
GraphObserver:

 0

DigitObserver: 11
GraphObserver:
***********
 11
***********
DigitObserver: 13
GraphObserver:
*************
 13
*************
DigitObserver: 16
GraphObserver:
****************
 16
****************
DigitObserver: 28
GraphObserver:
****************************
 28
****************************
DigitObserver: 22
GraphObserver:
**********************
 22
**********************
DigitObserver: 30
GraphObserver:
******************************
 30
******************************
DigitObserver: 44
GraphObserver:
********************************************
 44
********************************************
DigitObserver: 3
GraphObserver:
***
 3
***

拓展思路的要点

这里也出现了可替换性

在设计模式中,可替换性是指一个对象可以被另一个具有相同接口的对象替换,而不影响系统的功能。在观察者模式中,观察者(Observer)和被观察者(Subject)之间的关系是松耦合的,这意味着我们可以轻松地替换观察者或被观察者,而不会影响系统的其他部分。

Observer 的顺序

Subject 角色(被观察对象)中注册有多个 Observer 角色。在示例程序的 notifyObservers 方法中,先注册的 Observer 的 update 方法会先被调用。通常,在设计 ConcreteObserver 角色的类时,需要注意这些 Observer 的 update 方法的调用顺序,不能因为方法的调用顺序发生改变而产生问题。例如,在示例程序中,绝不能因为先调用 DigitObserverupdate 方法后调用 GraphObserverupdate 方法而导致应用程序不能正常工作。当然,通常,只要保持各个类的独立性,就不会发生上面这种类的依赖关系混乱的问题。不过,我们还需要注意下面将要提到的情况。

当 Observer 的行为会对被观察对象产生影响时

当观察者的行为会对被观察对象产生影响时,我们需要特别小心,以避免循环依赖和无限递归。例如,一个观察者在接收到通知后修改了被观察对象的状态,这可能会导致被观察对象再次通知所有观察者,从而引发无限循环。为了解决这个问题,我们可以引入一个标志位来跟踪通知状态,或者使用更复杂的事件处理机制。

传递更新信息的方式

在观察者模式中,传递更新信息的方式有多种选择。最简单的方法是直接调用观察者的更新方法,并将被观察对象自身作为参数传递。另一种方法是传递具体的更新信息,例如事件对象或数据包。这种方法可以减少观察者对被观察对象的依赖,从而提高系统的灵活性和可维护性。

从观察变为通知

在某些情况下,我们可能需要从观察模式转变为通知模式。例如,当系统中有大量的观察者时,逐个通知每个观察者可能会导致性能问题。此时,我们可以考虑使用事件总线或消息队列来集中处理通知,从而提高系统的性能和可扩展性。

MVC (Model/View/Controller)

观察者模式在 MVC 架构中得到了广泛应用。模型(Model)作为被观察对象,视图(View)作为观察者,当模型的状态发生变化时,视图会自动更新。控制器(Controller)负责协调模型和视图之间的交互。通过使用观察者模式,MVC 架构实现了视图和模型的松耦合,从而提高了系统的可维护性和可扩展性。

相关的设计模式

  • Mediator 模式