起步
模板模式全称应该是模板方法模式(Template method pattern),属于行为型。其利用继承关系,实现代码复用,同时确保子类扩展能力。
在这里我用 “子类扩展能力” 属于一家之言,主要想表达,模板模式在一定程度上限制了子类的扩展方法,或者说要求子类在预设的扩展点上进行功能扩展。
假设场景
模板方法模式要求在模板方法中定义程序框架或算法,并允许子类重写或自定义算法中的某些步骤,使得算法里的部分操作延迟到子类中实现。
用文字阐述或许让人懵逼,但是一看代码就“懂了”,事实上我们常常在代码里使用。
现在假设项目中需要增加一个解析配置的功能,核心的解析代码交由 Parser 完成,我们是上层调用者,负责提供配置文件的内容,将内容交给 Parser,返回解析好的配置信息。代码呈现如下:
class ConfigSource(object):
def __init__(self, parser):
self.parser = parser
def load(self, file):
# 1. 获取配置内容
f = open(file)
content = f.read()
# 2. 解析配置
config = self.parser.parse(content)
# 3. 释放资源
f.close()
return config
Good! But ... 试想一下,要是配置文件不在本地呢?比方说配置信息可能是写在 redis 里的,或者是通过 http 协议提供的,ConfigSource.load 明显不适用。
模板模式
首先我们需要一个模板方法,这个方法提供具体的操作步骤。步骤可以粗略分三个阶段:1. 配置内容的来源,2. 解析配置,3. 配置信息的去向。
class ConfigSource(metaclass=abc.ABCMeta):
def __init__(self, parser):
self.parser = parser
@abc.abstractmethod
def read_config(self):
pass
@abc.abstractmethod
def send_config(self, config):
pass
def load(self, file):
# 1. 来源
content = self.read_config()
# 2. 解析配置
config = self.parser.parse(content)
# 3. 去向
return self.send_config(config)
负责控制流程的 ConfigSource.load 方法我们称之为 “模板方法”,这就是模板方法模式称呼的来源。
假设配置文件就在本地,解析出来的配置信息放在内存中,则代码可以设计如下:
class LocalConfigSource(ConfigSource):
def read_config(self):
# 读取本地文件
with open("config_file_path", "r") as f:
return f.read()
def send_config(self, config):
# 直接返回
return config
而如果配置文件在远端,我们需要借助 tcp 协议获取远端的配置文件,完成解析后再发往远端。
class RemoteConfigSource(ConfigSource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
... # tcp sock、bind、listen、accept 等操作
self.conn = ...
def read_config(self):
config = self.conn.read(1024 * 10)
return config.decode("utf-8")
def send_config(self, config):
byte_arry = config.encode("utf-8")
self.conn.send(byte_arry, len(config))
self.conn.close()
总结
模板模式是常见而简单的设计模式,对使用者的“抽象”能力要求很高。一旦抽象得不够好,很可能造成子类重写模板方法,就得不偿失了。
在我看来,模板模式很有些控制反转的味道,在各大框架中频繁出现,值得重视。
参考
- 感谢 极客时间王争老师的《设计模式之美:模板模式》
- 感谢《Learning Python Design Patterns, 2nd Edition.pdf》
还不快抢沙发