建造者模式和工厂模式 3/30 | Python 主题月
写在前面
本文正在参加「Python主题月」,详情查看活动链接
这个月是 Python 活动月,我决定尝试用 Python 来刷这 30 天的每日一题和随机一题。然后如果周末有精力,我想捣鼓捣鼓这个python-patterns
设计模式对我来说更多的是学习而不是我的个人经验总结,所以我很可能理解偏,如果有大佬见到了请及时指出,我之所以选择在掘金来写一些个人的东西是因为这里的文章质量更高,我不希望后来者看到了这些文章被误导。
建造者模式
建造者模式将复杂对象的创建和表示解耦,这样就可以重复使用相同的过程来创建同一族的对象。当你必须将一个对象的规范与它的实际表示分离时,这很有用(通常是为了抽象)。这个概念太简略了,而且没有例子不是很好理解,先看看人家给的例子
# Abstract Building
class Building:
def __init__(self):
self.build_floor()
self.build_size()
def build_floor(self):
raise NotImplementedError
def build_size(self):
raise NotImplementedError
def __repr__(self):
return "Floor: {0.floor} | Size: {0.size}".format(self)
# Concrete Buildings
class House(Building):
def build_floor(self):
self.floor = "One"
def build_size(self):
self.size = "Big"
class Flat(Building):
def build_floor(self):
self.floor = "More than One"
def build_size(self):
self.size = "Small"
# In some very complex cases, it might be desirable to pull out the building
# logic into another function (or a method on another class), rather than being
# in the base class '__init__'. (This leaves you in the strange situation where
# a concrete class does not have a useful constructor)
class ComplexBuilding:
def __repr__(self):
return "Floor: {0.floor} | Size: {0.size}".format(self)
class ComplexHouse(ComplexBuilding):
def build_floor(self):
self.floor = "One"
def build_size(self):
self.size = "Big and fancy"
def construct_building(cls):
building = cls()
building.build_floor()
building.build_size()
return building
def main():
"""
>>> house = House()
>>> house
Floor: One | Size: Big
>>> flat = Flat()
>>> flat
Floor: More than One | Size: Small
# Using an external constructor function:
>>> complex_house = construct_building(ComplexHouse)
>>> complex_house
Floor: One | Size: Big and fancy
"""
if __name__ == "__main__":
import doctest
doctest.testmod()
复制代码
第一个例子是通过使用一个抽象的基类来实现的,其中初始化器(__init__
方法)指定了所需的步骤,而具体的子类则实现了这些步骤。在其他编程语言中,有时需要一个更复杂的安排。特别是在 C++ 中,不能在构造函数中拥有多态行为,见
stackoverflow.com/questions/1…
这意味着这种 Python 技术将无法工作。所需的多态性必须由一个外部的、已经构造好的不同类的实例来提供。一般来说,在 Python 中这是没有必要的,但我们也包括了第二个例子,显示了这种安排。
在一些非常复杂的情况下,把构建逻辑拉出来放到另一个函数中(或另一个类上的方法),而不是放在基类的 __init__
中,可能是可取的。(这就使你处于一种奇怪的情况,即一个具体的类没有一个有用的构造函数)
实际使用里假如说一个类的构造函数里面有一大堆可选参数(总参数超过4~5个,可选参数超过3个这样,看组里大佬的习惯),就可以考虑使用建造者模式了。我觉得这个模式和前面的抽象工厂有点难以区分。建造者模式应该是提供了不同层次的抽象,我就还拿小面馆举例子了,有些人喜欢吃过水面,有些人不喜欢过水面,有些面是汤面,有些面是干拌面,假如小面馆提供的最多的是过水面,干拌面,加菜码,之前并没有给过水不过水这个加一个 bool 变量来控制,然后默认干拌面但是也卖汤面,然后汤面加汤的这个步骤是写在加菜码那里的,现在小面馆生意好了,点单的代码要重构。这时候就可以上建造者模式了,规定一下干面和汤面之前的一个抽象的盛面动作,不过水,不加汤。然后具体的盛干面这个具体建造者是可选过水,加汤为 false,盛汤面这个具体建造者是可选过水,加汤可选加原面汤或者牛肉汤之类的枚举这样。
工厂模式
工厂是一个创建其他对象的对象。简单吧,看代码,不要想太多,工厂模式是最常用的设计模式之一,因为它简单,所以也有很多变体,在这些变体中“工厂”这个名词的含义都是不变的。
class GreekLocalizer:
"""A simple localizer a la gettext"""
def __init__(self) -> None:
self.translations = {"dog": "σκύλος", "cat": "γάτα"}
def localize(self, msg: str) -> str:
"""We'll punt if we don't have a translation"""
return self.translations.get(msg, msg)
class EnglishLocalizer:
"""Simply echoes the message"""
def localize(self, msg: str) -> str:
return msg
def get_localizer(language: str = "English") -> object:
"""Factory"""
localizers = {
"English": EnglishLocalizer,
"Greek": GreekLocalizer,
}
return localizers[language]()
def main():
"""
# Create our localizers
>>> e, g = get_localizer(language="English"), get_localizer(language="Greek")
# Localize some text
>>> for msg in "dog parrot cat bear".split():
... print(e.localize(msg), g.localize(msg))
dog σκύλος
parrot parrot
cat γάτα
bear bear
"""
if __name__ == "__main__":
import doctest
doctest.testmod()
复制代码
代码显示了一种将单词本地化翻译为两种语言的方法:英语和希腊语。get_localizer
是一个工厂函数,它根据所选择的语言构建一个本地化翻译器。根据翻译的语言,本地化翻译器对象来自不同类的实例。然而主代码不必担心哪个本地化翻译器将被实例化,因为“本地化”方法将以同样的方式被调用,与语言(英语还是希腊语)无关。
工厂方法可以在流行的网络框架 Django 中看到:
django.wikispaces.asu.edu/*NEW*+Djang…
例如,在一个网页的联系表单中,主题和消息字段是使用同一个表单工厂(CharField()
)创建的,尽管它们根据用途有不同的实现。
小结
建造者模式将复杂对象的创建和表示解耦,在类的构造函数有多个可选参数时可以选用。
工厂模式太常见了,我觉得哪里都能用,就是看用的巧不巧。