Observer 观察者模式

设计模式 2020-05-02 4061 字 92 浏览 点赞

起步

观察者模式属于行为型,旨在定义一个一对多关系,当一个对象状态发生改变时,所有依赖对象都会自动接收通知。

观察者模式属于抽象模式,要点不在于代码实现上,不同应用场景会有不同的实现方式,但要解决的问题不会变。

非观察模式

现在我们需要设计一个读取配置文件的功能。这个功能负责读取文件,然后解析读取到的内容。配置解析前的格式:

【Base】
...

【DB】
...

【Test】
...

配置解析后的格式:

{
  "Base": ...,
  "DB": ...,
  "Test": ...,
}

基于接口编程的代码设计如下:

class Source(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read_and_extract(self, path):
        pass

class ConfigSource(Source):
    def read_and_extract(self, path):
        """ 读取并解析配置文件 """
        ...  # 数据处理
        return {
            "Base": ...,
            "DB": ...,
            "Test": ...,
        }

现在 ConfigSource.read_and_extract 方法返回完整的配置信息,但我们希望项目中使用配置的时候传递的对象能更单一些,也就是数据库对象只能读取数据库的配置,测试代码只能读取测试配置。

基于上述需求,需添加三个具体类,分别是 DBConfig,BaseConfig,TestConfig;具体类需要实现 update 方法,用于获取最新的配置信息,实例属性 self.config 负责存储配置信息:

class Config(metaclass=abc.ABCMeta):
    def __init__(self):
        self.config = None

    @abc.abstractmethod
    def update(self, whole_config_info):
        pass

class DBConfig(Config):
    def update(self, whole_config_info):
        self.config = whole_config_info["DB"]

class BaseConfig(Config):
    def update(self, whole_config_info):
        self.config = whole_config_info["Base"]

class TestConfig(Config):
    def update(self, whole_config_info):
        self.config = whole_config_info["Test"]

项目中的使用方式:

# 初始化对象
cs = ConfigSource()
base_config = BaseConfig()
db_config = DBConfig()
test_config = TestConfig()

config = cs.read_and_extract("/var/config")  # 读取并解析配置
# 更新配置信息
base_config.update(config)
db_config.update(config)
test_config.update(config)

乍一看觉得没啥问题,Source 的具体类与 Config 的具体类也做到了足够解耦。但有没有发现,只要我们调用了 read_and_extract 将本地配置更新内存中,就必须依次调用:base_config.updatedb_config.updatetest_config.update。否则项目里使用的配置信息就不是最新。

这就造成了对象使用上的耦合,或者换句话,客户端用起来很不方便。而功能扩展之后,会造成一长串的 xxx.update,有代码冗余的嫌疑。

观察者模式

从功能角度来说,Config 类(DBConfig、BaseConfig、TestConfig)需要根据 Source 类(ConfigSource)的改变而改变,即:Config 作为观察者,负责观察 Source 的状态;Source 是被观察者。这其实就是一个极为典型的观察者模式的使用场景。回顾观察者模式的概念:当一个对象状态发生改变时,所有依赖对象都会自动接收通知

在我们之前完成的代码设计中,缺点就是没有让 Source 发生改变时,其依赖对象 Config 接收到通知。现在依据观察者模式来优化代码设计。

class Source(metaclass=abc.ABCMeta):
    def __init__(self):
        self.config = None
        self.observers = None

    def register_observers(self, observers: List):
        """ 注册观察者 """
        self.observers = observers

    def notify(self):
        """ 通知观察者 """
        if not self.observers:
            return

        for observer in self.observers:
            observer.update(self.config)

    @abc.abstractmethod
    def read_and_extract(self, path):
        pass

class ConfigSource(Source):
    def read_and_extract(self, path):
        ...  # 数据处理
        self.config = {
            "Base": ...,
            "DB": ...,
            "Test": ...,
        }
        return self.config

主要大改动是 Source 类,增加了注册观察者们的方法 register_observers,以及负责通知观察者的 notify。同时,read_and_extract 的处理结果除了 return 出去之外,还保存在实例属性 self.config 中,这是为了使用 notify 时避免没有必要的传参。

现在客户端使用 Config 具体类和 Source 具体类就简单许多了:

base_config = BaseConfig()
db_config = DBConfig()
test_config = TestConfig()

cs = ConfigSource()
# 注册观察者
cs.register_observers([base_config, db_config, test_config])
# 更新内存中的配置信息
cs.read_and_extract("/var/config")
cs.notify()

以后 每次执行完 read_and_extract 再调用 notify 就好。甚至你还可以将这两个方法封装进一个函数,此后只需要调用 read_and_extract_and_notify。

也许就有人说了,非观察者模式也能封装一个函数只调用一次呀!就设计模式来说,除了解耦之外,另一个优势是扩展。就算以后配置文件中的单元配置变多了,比如有了 【Develop】,【Web】等等配置,但你只需要实现 update 方法,然后注册到被观察者对象中,其他逻辑一律不用管,对扩展功能的程序员来说省了很多事。

总结

观察者模式是理解起来简单,但使用千变万化的设计模式,在实际开发场景中常常出现。例如现在各大 App 的消息推送,只要把所有用户注册进被观察者对象里(这里的被观察者就是消息中心),只要有新的消息添加进来,所有用户就能接收到。例如监听消息中间件 Redis ,当 Redis 中有数据写入时,相应的处理方法会被调用,这也一个观察者模式的体现。

所以学习观察者模式,需要多去各种开源项目中体会。

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论