设计模式-观察者模式

45 阅读1分钟

定义

观察者模式,是一种行为设计模式,它允许将被观察者管理其观察者列表,并在状态变化时通知它们。

这种模式在实现事件监听和发布/订阅系统中非常有用。

UML 类图

typescript 实现

1. 定义观察者/订阅者接口

interface Subscriber {
  update(publisher: Publisher): void;
}

2. 创建被观察者/发布者类

interface Publisher {
  subscribe(subscriber: Subscriber): void;
  unsubscribe(subscriber: Subscriber): void;
  notify(): void;
}

class ConcretePublisher implements Publisher {
  private subscribers: Subscriber[] = [];
  private state: number;
  
  public getState(): number {
    return this.state;
  }
  public setState(state: number): void {
    this.state = state;
    this.notify();
  }
  public subscribe(subscriber: Subscriber): void {
    const isExist = this.subscribers.includes(subscriber);
    if(isExist) {
      console.log("Publisher: Subscriber has been subscribed already.");
    }
    console.log("Publisher: subscribe an subscriber.");
    this.subscribers.push(subscriber);
  }
  public unsubscribe(subscriber: Subscriber): void {
    const subscriberIndex = this.subscribers.indexOf(subscriber);
    if(subscriberIndex === -1) {
      return console.log("Publisher: Noexistent subscriber.");
    }
    this.subscribers.splice(subscriberIndex, 1);
    console.log("Publisher: unsubscribe an subscriber.");
  }
  public notify(): void {
    console.log("Publisher: Notifying subscribers...");
    for(const subscriber of this.subscribers) {
      subscriber.update(this);
    }
  }
}

3. 创建具体观察者类

class ConcreteSubscriber implements Subscriber {
  public update(publisher: Publisher): void {
    if(publisher instanceof ConcretePublisher && publisher.getState() < 3) {
      console.log("ConcreteSubscriber: Reached to the event.");
    }
  }
}

4. 使用示例

const publisher = new ConcretePublisher();
const subscriber = new ConcreteSubscriber();
publisher.subscribe(subscriber);

const subscriber2 = new ConcreteSubscriber();
publisher.subscribe(subscriber2);

publisher.setState(2);
publisher.setState(3);

publisher.unsubscribe(subscriber2);
publisher.setState(4);

通用实现

javascript 实现的事件模型,订阅者不必须是类(函数是第一等公民),下面基本是模仿 Event 系统

// 公共代码
export class EventTarget {
  private listeners: Record<
    string,
    Map<EventListenerObject | EventListener, AddEventListenerOptions>
  > = {};
  
  addEventListener(
    type: string,
    callback: EventListenerOrEventListenerObject,
    options?: AddEventListenerOptions | boolean
  ): void {
    if(!this.listeners[type]) {
      this.listeners[type] = new Map();
    }
    const eventListenerOptions: AddEventListenerOptions = typeof options === "object"
      ? options
      : {  capture: Boolean(options) }
    this.listeners[type]!.set(callback, eventListenerOptions);
    if(eventListenerOptions.signal) {
      eventListenerOptions.signal.addEventListener("abort", () => {
        this.removeEventListener(type, callback);
      });
    }
  }

  removeEventListener(
    type: string,
    callback: EventListenerOrEventListenerObject | null,
    options?: EventListenerOptions | boolean
  ): void {
    if(this.listeners[type]) {
      this.listeners[type]!.delete(callback as EventListener);
    }
  }
  
  dispatchEvent(event: Event): void {
    if(event.type in this.listeners) {
      const eventListeners = Array.from(this.listeners[event.type]);
      for(const [listener, options] of eventListeners) {
        if(options.capture && [0, 1, 2].includes(event.eventPhase)) {
          if(typeof listener === "object" && listener.handleEvent) {
            listener.handleEvent.call(listener, event);
          } else if (typeof listener === "function") {
            listener.call(this, event);
          }
        } else if(!options.capture && [0, 3, 2].includes(event.eventPhase)) {
          if(typeof listener === "object" && listener.handleEvent) {
            listener.handleEvent.call(listener, event);
          } else if(typeof listener === "function") {
            listener.call(this, event);
          }
        }
        if(options.once) {
          this.removeEventListener(event.type, listener);
        }
      }
    }
  }
}

// 私有代码,使用示例
class ButtonElement extends EventTarget {
}

const btn = new ButtonElement();

btn.addEventListener("click", () => {
  console.log("click button");
});

function handleClick() {
  btn.dispatchEvent(new Event("click"));
}

handleClick();