起步
观察者模式属于行为型,旨在定义一个一对多关系,当一个对象状态发生改变时,所有依赖对象都会自动接收通知。
观察者模式属于抽象模式,要点不在于代码实现上,不同应用场景会有不同的实现方式,但要解决的问题不会变。
非观察模式
现在我们需要设计一个读取配置文件的功能。这个功能负责读取文件,然后解析读取到的内容。配置解析前的格式:
【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.update
,db_config.update
,test_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 中有数据写入时,相应的处理方法会被调用,这也一个观察者模式的体现。
所以学习观察者模式,需要多去各种开源项目中体会。
参考
- 感谢 极客时间王争老师的《设计模式之美:观察者模式》
还不快抢沙发