观察者模式(Observer pattern)学习笔记

Observer pattern 观察者模式学习笔记

AssistedByChatGPT

前言

Design pattern的第二篇,学习教材使用的是Refactoring.Guru,为了提高效率会和ChatGPT并用,同时自己会再加上一些自己的理解和应用实例。 开始学习Design pattern的奇迹和想法写在这里

观察者模式简介

观察者模式(Observer Pattern)定义了对象之间的一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。观察者模式主要用于实现分布式事件处理系统、消息发布/订阅系统,以及在MVC框架中模型与视图的分离。

观察者模式的组成

观察者模式通常由以下几个核心组成部分构成:

  • 主题(Subject): 也称为被观察者,它维护一系列的观察者,提供用于增加和删除观察者的接口。
  • 观察者(Observer): 为那些在主题状态发生改变时需获得通知的对象定义一个更新接口。
  • 具体主题(Concrete Subject): 存储状态,当状态发生改变时,向其观察者发出通知。
  • 具体观察者(Concrete Observer): 实现观察者接口,以便在主题状态改变时更新自身状态。

工作原理

  1. 注册观察者: 观察者向主题注册,表明它想要接收关于主题状态变更的通知。
  2. 状态更新: 当主题的状态发生变化时,主题会遍历注册的观察者列表,向它们发送通知。
  3. 接收通知: 观察者收到通知后,根据通知中的信息或者通过查询主题的最新状态,来更新自己的状态。

辅助理解

对于现实中的例子,可以通过订阅报纸或者邮件来对这个Design pattern的流程进行理解: 有一个报纸出版社(主题/Subject),它定期出版报纸。有许多人(观察者/Observer)对这份报纸感兴趣,因此他们向出版社订阅报纸。一旦新的报纸出版了,出版社就会将其发送给所有订阅者。

  • 出版社:类似于观察者模式中的主题(Subject)。出版社知道谁订阅了报纸,并在有新报纸时通知他们。
  • 订阅者:相当于观察者(Observer)。订阅者想要知道何时有新报纸出版,并期待收到它。
  • 报纸:可以看作是通知本身,或者是状态的改变,这导致观察者(订阅者)执行某些动作(如阅读报纸)。

对应到观察者模式的实现

  • 订阅(Attach):人们向出版社订阅报纸,相当于观察者向主题注册自己,表明他们想要接收状态更新。
  • 退订(Detach):如果某人不再想接收报纸,他们可以发送TD以取消订阅。这类似于观察者从主题的观察者列表中移除自己。
  • 分发(Notify):一旦新报纸出版,出版社就会将其分发给所有的订阅者。这对应于主题状态发生改变时,主题遍历其所有观察者并通知它们。

示例实现:Python

对于实现例子可以看看原文中的各种语言的实现方法:Observer in Python

这里贴上Python的实现方法

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from __future__ import annotations
from abc import ABC, abstractmethod
from random import randrange
from typing import List


class Subject(ABC):
    """
    The Subject interface declares a set of methods for managing subscribers.
    """

    @abstractmethod
    def attach(self, observer: Observer) -> None:
        """
        Attach an observer to the subject.
        """
        pass

    @abstractmethod
    def detach(self, observer: Observer) -> None:
        """
        Detach an observer from the subject.
        """
        pass

    @abstractmethod
    def notify(self) -> None:
        """
        Notify all observers about an event.
        """
        pass


class ConcreteSubject(Subject):
    """
    The Subject owns some important state and notifies observers when the state
    changes.
    """

    _state: int = None
    """
    For the sake of simplicity, the Subject's state, essential to all
    subscribers, is stored in this variable.
    """

    _observers: List[Observer] = []
    """
    List of subscribers. In real life, the list of subscribers can be stored
    more comprehensively (categorized by event type, etc.).
    """

    def attach(self, observer: Observer) -> None:
        print("Subject: Attached an observer.")
        self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        self._observers.remove(observer)

    """
    The subscription management methods.
    """

    def notify(self) -> None:
        """
        Trigger an update in each subscriber.
        """

        print("Subject: Notifying observers...")
        for observer in self._observers:
            observer.update(self)

    def some_business_logic(self) -> None:
        """
        Usually, the subscription logic is only a fraction of what a Subject can
        really do. Subjects commonly hold some important business logic, that
        triggers a notification method whenever something important is about to
        happen (or after it).
        """

        print("\nSubject: I'm doing something important.")
        self._state = randrange(0, 10)

        print(f"Subject: My state has just changed to: {self._state}")
        self.notify()


class Observer(ABC):
    """
    The Observer interface declares the update method, used by subjects.
    """

    @abstractmethod
    def update(self, subject: Subject) -> None:
        """
        Receive update from subject.
        """
        pass


"""
Concrete Observers react to the updates issued by the Subject they had been
attached to.
"""


class ConcreteObserverA(Observer):
    def update(self, subject: Subject) -> None:
        if subject._state < 3:
            print("ConcreteObserverA: Reacted to the event")


class ConcreteObserverB(Observer):
    def update(self, subject: Subject) -> None:
        if subject._state == 0 or subject._state >= 2:
            print("ConcreteObserverB: Reacted to the event")


if __name__ == "__main__":
    # The client code.

    subject = ConcreteSubject()

    observer_a = ConcreteObserverA()
    subject.attach(observer_a)

    observer_b = ConcreteObserverB()
    subject.attach(observer_b)

    subject.some_business_logic()
    subject.some_business_logic()

    subject.detach(observer_a)

    subject.some_business_logic()

示例实现-Unity

假设我们正在开发一个游戏,其中玩家的生命值(HP)是游戏状态的一部分。我们希望当玩家的生命值发生变化时,游戏UI上的生命条能够相应地更新。这是观察者模式的一个典型应用场景。

步骤 1: 定义Subject

首先,我们需要一个Subject(主题),它代表玩家的状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Player : MonoBehaviour {
    public delegate void HealthChanged(float currentHealth);
    public event HealthChanged OnHealthChanged;
    private float health = 100;

    public void TakeDamage(float damage) {
        health -= damage;
        health = Mathf.Max(health, 0);
        OnHealthChanged?.Invoke(health);
    }

    // 其他玩家相关的方法
}

在这个Player类中,我们定义了一个事件OnHealthChanged,这个事件就是我们的通知机制。当玩家受到伤害并且生命值发生变化时,我们触发这个事件。

步骤 2: 定义Observer

接下来,我们需要定义Observer(观察者),在这个例子中,观察者将是UI系统,特别是显示玩家生命值的组件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class HealthBar : MonoBehaviour {
    private Player player;

    void Start() {
        player = FindObjectOfType<Player>();
        player.OnHealthChanged += UpdateHealthBar;
    }

    void UpdateHealthBar(float health) {
        // 更新生命条UI
        Debug.Log("Health updated to: " + health);
    }

    void OnDestroy() {
        player.OnHealthChanged -= UpdateHealthBar;
    }
}

HealthBar类中,我们订阅了Player类的OnHealthChanged事件。这意味着,每当玩家的生命值发生变化时,UpdateHealthBar方法将被自动调用,我们可以在这个方法中更新生命条的显示。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus