74

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

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

4

6 回答 6

119

设计模式通常可以用内置的语言特性代替。

您的用例

你说“我想要一个更易读的“手段”来实例化一个有很多参数的类。”。在 Java 的情况下:

[A] 构建器模式的用例是当要构建的对象的构造器必须采用非常多的参数时。setMaxTemperature(int t)在这种情况下,将此类配置参数集中在构建器对象( 、setMinTemperature(int t)、 .. 等)中通常set比让调用者负担一长串参数来传递类的构造函数更方便。.

不需要构建器模式

但是 Python 支持命名参数,所以这不是必需的。您可以只定义一个类的构造函数:

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(f"Hello({i})" for i in range(100))
于 2012-08-15T21:16:45.753 回答
49

OP 通过将 Builder 模式转换为特定于 Java 的方式来为自己做好准备。它不是。它在四人帮的书中,并且可能与任何面向对象的语言相关。

不幸的是,即使是关于 Builder 模式的 Wikipedia 文章也没有给予足够的信任。它不仅仅对代码优雅有用。构建器模式是创建不可变对象的好方法,这些对象在使用之前必须是可变的。不可变状态在函数范式中尤其重要,这使得 Builder 成为 Python 的优秀面向对象模式。

我在下面使用collections.namedtuple提供了一个示例 Builder + ImmutableObject 实现,从“ How to make an immutable object in python ”中借用和修改。我让 Builder 相当简单。但是,可以提供返回 Builder 本身的 setter 函数以允许调用链。或者可以在 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 上设置和删除属性会引发错误。

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

于 2014-10-04T12:51:08.413 回答
24

我不同意@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()
于 2014-03-05T18:04:51.093 回答
8

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

我相信使用装饰器来指定构建器类是在 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)
于 2020-06-22T14:42:54.853 回答
4

Java 中的构建器模式可以通过使用以下变体在 python 中轻松实现:

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

whererequiredsomeNumber是一个示例,显示具有默认值的所需参数,然后在处理可能存在的情况时读取可变参数None

如果您之前没有使用过变量参数,请参考这里

于 2012-08-15T21:17:59.497 回答
4

builder和constructor不是一回事,builder是一个概念,constructor是一种编程语法。没有必要将两者进行比较。

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

从概念上讲,构建器模式将构建过程与最终对象分离。以建造房屋的真实世界为例。建造者可能会使用很多工具和材料来建造房屋,但最终的房屋在建造后不需要那些工具和多余的材料。

例子:

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

house = HouseBuilder.drills(drills).woodboards(woodboards).bricks(bricks).build()
于 2020-06-30T08:07:58.267 回答