Python中的Builder模式等效

时间:2012-08-15 21:05:11

标签: python design-patterns builder-pattern

在Java中,您可以使用builder pattern提供更具可读性的方法来实例化具有许多参数的类。在构建器模式中,构造一个配置对象,其中包含设置命名属性的方法,然后使用它来构造另一个对象。

Python中的等价物是什么?是模仿相同实现的最佳方式吗?

6 个答案:

答案 0 :(得分:85)

设计模式通常可以替换为内置语言功能。

您的用例

你说“我想要更具可读性”意味着“实例化一个包含许多参数的类”。在Java的情况下:

  

[A] use case for the builder pattern is when the constructor of the object to be built must take very many parameters. In such cases, it is often more convenient to lump such configuration parameters in a builder object (setMaxTemperature(int t), setMinTemperature(int t), set.. , etc. ) than to burden the caller with a long list of arguments to pass in the class's constructor.

不需要构建器模式

但是Python支持named parameters,所以这不是必需的。你可以定义一个类的构造函数:

class SomeClass(object):
    def __init__(self, foo="default foo", bar="default bar", baz="default baz"):
        # do something

并使用命名参数调用它:

s = SomeClass(bar=1, foo=0)

请注意,您可以自由地重新排序和省略参数,就像使用Java中的构建器一样,您可以省略或重新排序对构建器对象上的set方法的调用。

另外值得一提的是,Python的动态特性使您可以更自由地构建对象(使用__new__等),这可以取代构建器模式的其他用途。

但如果你真的想用它

您可以使用collections.namedtuple作为配置对象。 namedtuple()返回一个表示元组的新类型,每个元组的参数都有一个给定的名称,而不必编写样板类。您可以使用与Java构建器类似的方式使用结果类型的对象。 (感谢Paul McGuire建议。)

StringBuilder

相关模式是Java的StringBuilder,它用于分阶段有效地构造(不可变的)String。在Python中,可以用str.join替换它。例如:

final StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++)
    sb.append("Hello(" + i + ")");
return sb.toString();

可以替换为

return "".join("Hello({})".format(i) for i in range(100))

答案 1 :(得分:33)

OP通过将Builder模式转换为特定于Java的模式来设置自己的崩溃。不是。它位于Gang of Four's book中,可能与任何面向对象的语言相关。

不幸的是,即使Wikipedia article on the Builder pattern也没有给予足够的信任。它不仅仅对代码优雅有用。 构建器模式是创建不可变对象的好方法,这些对象在被使用之前需要是可变的。不可变状态在功能范例中尤其重要,使得Builder成为python的一个出色的面向对象模式

我在下面使用collections.namedtuple提供了一个示例Builder + ImmutableObject实现,从&#34; How to make an immutable object in python&#34;借用和修改。我保持Builder非常简单。但是,可以提供setter函数,返回Builder本身以允许调用链接。或者可以在Builder中使用@property语法来提供在设置之前检查属性有效性的属性设置器。

from collections import namedtuple

IMMUTABLE_OBJECT_FIELDS = ['required_function_result', 'required_parameter', 'default_parameter']

class ImmutableObjectBuilder(object):
    def __init__(self, required_function, required_parameter, default_parameter="foo"):
        self.required_function = required_function
        self.required_parameter = required_parameter
        self.default_parameter = default_parameter

    def build(self):
        return ImmutableObject(self.required_function(self.required_parameter),
                               self.required_parameter,
                               self.default_parameter)

class ImmutableObject(namedtuple('ImmutableObject', IMMUTABLE_OBJECT_FIELDS)):
    __slots__ = ()

    @property
    def foo_property(self):
        return self.required_function_result + self.required_parameter

    def foo_function(self):
        return self.required_function_result - self.required_parameter

    def __str__(self):
        return str(self.__dict__)

使用示例:

my_builder = ImmutableObjectBuilder(lambda x: x+1, 2)
obj1 = my_builder.build()
my_builder.default_parameter = "bar"
my_builder.required_parameter = 1
obj2 = my_builder.build()
my_builder.required_function = lambda x: x-1
obj3 = my_builder.build()

print obj1
# prints "OrderedDict([('required_function_result', 3), ('required_parameter', 2), ('default_parameter', 'foo')])"
print obj1.required_function_result
# prints 3
print obj1.foo_property
# prints 5
print obj1.foo_function()
# prints 1
print obj2
# prints "OrderedDict([('required_function_result', 2), ('required_parameter', 1), ('default_parameter', 'bar')])"
print obj3
# prints "OrderedDict([('required_function_result', 0), ('required_parameter', 1), ('default_parameter', 'bar')])"

在这个例子中,我创建了三个ImmutableObjects,它们都有不同的参数。我已经让调用者能够以构建器的形式复制,修改和传递可变配置,同时仍然保证构建对象的不变性。在ImmutableObjects上设置和删除属性会引发错误。

底线:构建器是一种很好的方式来传递具有可变状态的东西,当你准备好使用它时,它提供了一个具有不可变状态的对象。或者,换句话说,构建器是提供属性设置器同时仍然确保不可变状态的好方法。这在功能范例中尤为重要。

答案 2 :(得分:12)

我不同意@MechanicalSnail。我认为类似于海报所引用的构建器实现在某些情况下仍然非常有用。命名参数只允许您简单地设置成员变量。如果你想做一些稍微复杂的事情,那你就不走运了。在我的示例中,我使用经典构建器模式来创建数组。

class Row_Builder(object):
  def __init__(self):
    self.row = ['' for i in range(170)]

  def with_fy(self, fiscal_year):
    self.row[FISCAL_YEAR] = fiscal_year
    return self

  def with_id(self, batch_id):
    self.row[BATCH_ID] = batch_id
    return self

  def build(self):
    return self.row

使用它:

row_FY13_888 = Row_Builder().with_fy('FY13').with_id('888').build()

答案 3 :(得分:4)

使用以下变量:

可以在python中轻松实现Java中的构建器模式

MyClass(self, required=True, someNumber=<default>, *args, **kwargs)

其中requiredsomeNumber是一个示例,用于显示具有默认值的必需参数,然后在处理可能存在None

的情况时读取变量参数

如果您之前未使用过变量参数,请参阅this

答案 4 :(得分:2)

我刚遇到建立这种模式的需要,偶然发现了这个问题。我意识到这个问题有多老了,但是如果对其他人有用的话,不妨添加我的构建器模式版本。

我相信使用装饰器指定构建器类是在python中实现构建器模式的最符合人体工程学的方法。

def buildermethod(func):
  def wrapper(self, *args, **kwargs):
    func(self, *args, **kwargs)
    return self
  return wrapper

class A:
  def __init__(self):
    self.x = 0
    self.y = 0

  @buildermethod
  def set_x(self, x):
    self.x = x

  @buildermethod
  def set_y(self, y):
    self.y = y

a = A().set_x(1).set_y(2)

答案 5 :(得分:1)

构建器和构造器不是同一件事,构建器是一个概念,构造器是一种编程语法。没有意义将两者进行比较。

因此,请确保您可以使用构造函数,类方法或专用类来实现构建器模式,没有冲突,请使用适合您情况的任何一种。

从概念上讲,构建器模式使构建过程与最终对象脱钩。以建造房屋的真实世界为例。建造者可能会使用很多工具和材料来建造房屋,但最终房屋不必在建造后就摆放这些工具和多余的材料。

示例:

woodboards = Stores.buy(100)
bricks = Stores.buy(200)
drills = BuilderOffice.borrow(4)

house = HouseBuilder.drills(drills).woodboards(woodboards).bricks(bricks).build()