起步
命令模式属于创建型,旨在解决多参数初始化类带来的复杂设计问题。
“多参数初始化类”的意思就是,实例化一个对象时需要传入大量的变量。另一方面,也许这些传入的变量之间存在一定的关系约束,比如需要构建一个正方形对象,那就要求传入的长宽值相等。
类的设计问题
现在我们需要设计一个读取配置的类,要求指定配置文件的路径。这个需求很简单,代码很好实现:
class ConfigSource(object):
def __init__(self, path):
self._path = path # 文件路径
def load(self):
""" 加载配置 """
...
后来需求增加,譬如要求判断配置文件的时效性,如果不在时效期内,不读文件;要求判断客户端的使用权限,如果是普通用户,部分配置内容不可读(不做解析)。对应的设计就是:
class ConfigSource(object):
def __init__(self, path, time_range, ower):
self._path = path # 文件路径
self._time_range = time_range
self._ower = ower
def load(self):
...
上述代码看上去还好,然后类似的需求继续累加很可能出现这样一种场景:
class ConfigSource(object):
def __init__(self, path, time_range, ower, a, b, c, d, e, f):
self._path = path # 文件路径
self._time_range = time_range
self._ower = ower
self._a = a
self._b = b
...
def load(self):
...
# 使用
cs = ConfigSource(path, time_range, ower, a, b, c, ...)
就是需要传入的初始化参数越来越多,实例化对象越来越繁琐,代码不好看了。
有什么法子可以减少实例化参数呢?我们完全可以用 set_xxx() 方法来解决。譬如:
class ConfigSource(object):
def __init__(self, path):
self._path = path
def set_time_range(self, time_range):
self._time_range = time_range
def set_ower(self, ower):
self._ower = ower
...
def load(self):
...
实例化参数被有效减少,可另一个问题也来了。现在要求 time\_range,ower 同时被传入。但是客户端通过 set\_xxx() 函数设置参数,存在只调用 set\_time\_range 而没有调用 set_ower 的情况,继而 load 方法的执行逻辑也会受影响。
既然初始化参数那么复杂,根据复杂就要分层的设计原则,我们应该把生成初始化参数这事交给别人来做。这个别人不是他人,就是马上要说的建造者。
建造者模式
建造者模式要求把复杂类的构造器独立出来,在一定程度上降低了原来类的复杂度。上述代码可修改为:
class ConfigSourceBuilder(object):
def set_path(self, path):
if not os.path.exists(path):
raise TypeError()
self.path = path
return self
def set_time_range(self, time_range):
if not time_range:
raise TypeError()
self.time_range = time_range
return self
def set_ower(self, ower):
if not ower:
raise TypeError()
self.ower = ower
return self
...
def build(self):
""" 构造初始化参数对象(参数校验职责) """
if not all([self.path, self.time_range, self.ower]):
raise TypeError()
return self
class ConfigSource(object):
def __init__(self, builder):
self._path = builder.path # 文件路径
self._time_range = builder.time_range # 失效范围
self._ower = builder.ower # 所有者
def load(self):
...
对应的客户端用法:
builder = (
ConfigSourceBuilder()
.set_path(...)
.set_time_range(...)
.set_ower(...)
.build()
)
cs = ConfigSource(builder)
总结
事实上建造者模式在代码实现细节上可以更多样化,只要记住核心思想:隔离类与类的构造器。甚至你可以片面觉得 xxxBuilder 就是在行使参数校验的功能,而让原有类更多关注自己的功能逻辑。
建造者模式降低了原来类的复杂度,同时也增加了重复代码——大量相同的属性名将出现在两个类中。这也使得 ConfigSource 与 ConfigSourceBuilder 之间的耦合度很高。当然,你可以通过接口解耦,这就属于建造者模式的其他表现形式了。
参考
- 感谢 极客时间王争老师的《设计模式之美:建造者模式》
- 感谢 Builder pattern
还不快抢沙发