Python中的Builder模式等效

nam*_*ked 47 python design-patterns builder-pattern

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

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

Mec*_*ail 90

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

你的用例

你说"我想要更具可读性"意味着"实例化一个包含许多参数的类".在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
Run Code Online (Sandbox Code Playgroud)

并使用命名参数调用它:

s = SomeClass(bar=1, foo=0)
Run Code Online (Sandbox Code Playgroud)

请注意,您可以自由地重新排序和省略参数,就像使用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();
Run Code Online (Sandbox Code Playgroud)

可以替换为

return "".join("Hello({})".format(i) for i in range(100))
Run Code Online (Sandbox Code Playgroud)

  • @ mechanical-snail这可能只是我经验不足的问题,但我如何使用那些"简单,pythonic,不需要 - 不构建"的解决方案来解决类似StringBuilder的问题,其方法可以被多次调用,命令是重要的是,在返回完成的不可变产品之前? (4认同)
  • +1 表示将来可能会帮助他人的答案。 (2认同)
  • 作为一个注释,构建一个列表(或其他数据结构 - 通常使用列表推导或生成器),然后将iterable传递给构造函数是执行任何类型的等效于具有这种行为的构建器的首选方法. (2认同)

Mal*_*ina 34

OP通过将Builder模式转换为特定于Java的模式来设置自己的下降.不是.它出现在Gang of Four的书中,可能与任何面向对象的语言相关.

不幸的是,即使维基百科关于Builder模式的文章也没有给予足够的信任.它不仅仅对代码优雅有用.构建器模式是创建不可变对象的好方法,这些对象在使用之前需要是可变的.不可变状态在功能范例中尤其重要,使得Builder成为python的优秀面向对象模式.

我在下面使用collections.namedtuple提供了一个示例Builder + ImmutableObject实现,从" 如何在python中创建不可变对象 "中借用和修改.我让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__)
Run Code Online (Sandbox Code Playgroud)

用法示例:

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')])"
Run Code Online (Sandbox Code Playgroud)

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

结论:构建器是一种传递具有可变状态的东西的好方法,当你准备好使用它时,它提供了一个具有不可变状态的对象.或者,换句话说,构建器是提供属性设置器同时仍然确保不可变状态的好方法.这在功能范例中特别有价值.

  • 对于每个人在Python中折扣构建器的价值...在从其他答案和评论中假设构建器模式只是Java绒毛之前,请查看最佳示例.它不是关于Java.构建器模式是关于分离(1)对象的复杂构造,其通常包括执行来自(2)对象的后续使用的约束.这样,您要构建的对象不会被构建所需的方法污染.此外,结果对象不必是不可变的. (9认同)
  • 是的,不可变对象在Python中并不存在(实际上,它们在Java中也不存在:你总是可以抓住私有成员并通过反射来改变它们).但至少大会让你知道,如果你确实改变了状态,你应该期待Bad Things(TM).关于setter语法:它不那么pythonic,但更符合正式的Builder模式.通过返回构建器,您可以进行调用链接(调用者不必继续键入my_builder.),当您要设置许多参数时,这将变得非常方便.我认为这两种风格都是合理的. (4认同)
  • 我将实现修改为实际不可变对象(使用元组).我简化了Builder,同时指出了可用于实现Builder的其他选项.我最初的实现混淆了这一点:构建器是一种很好的方式来处理可变的东西,变成不可变的东西.希望这个实现更清晰,更干净. (3认同)
  • 为了回应较早的评论,即 Python 不涉及大量不可变对象,可能值得指出的是,如今,使用 [`@dataclass(frozen=True)`](https://docs.python) 定义不可变类型.org/3.11/library/dataclasses.html#dataclasses.dataclass)(在 2018 年发布的 Python 3.7 中引入)是惯用且常见的。 (3认同)
  • 我得到你所说的,但是在Python中遵循构建器模式没有任何价值 - 还有其他语言功能以更好(更易读)的方式完成相同的工作.我也没有真正看到模式如何使我不太可能直接将内容分配给其中一个内部值(与Java不同,你必须通过反射来访问它,你很明显正在规避原始程序员的意图). (2认同)

Cow*_*bop 13

我不同意@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
Run Code Online (Sandbox Code Playgroud)

使用它:

row_FY13_888 = Row_Builder().with_fy('FY13').with_id('888').build()
Run Code Online (Sandbox Code Playgroud)

  • 这根本不是真的 - 命名参数只是传递给构造函数 - 你可以在构造函数中用它们做任何你想做的事情,而不仅仅是设置属性. (4认同)
  • 是的,除了你可以在构造函数中做任何你想做的事情,所以你的断言 *'如果你想做一些稍微复杂的事情,你运气不好'* 是无效的。你可以用这些命名参数做任何你想做的事情,就像你可以用构建器一样,除了更多的pythonically。 (2认同)
  • 这已经过去很长时间了,但是对于将来阅读本文的人来说 - 参数实际上是传递给`__init__`,它是初始化器,而不是构造函数.python中的构造函数是神奇的类方法`__new__`,你通常不太习惯.在任何情况下,是的,建设者可能会有所帮助,我主要评论语义. (2认同)

小智 7

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

我相信使用装饰器来指定构建器类是在 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)
Run Code Online (Sandbox Code Playgroud)


som*_*ass 7

构建器和构造函数不是一回事,构建器是一个概念,构造函数是一种编程语法。没有必要比较两者。

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

从概念上讲,构建器模式将构建过程与最终对象解耦。举一个现实世界中建造房屋的例子。建造者可能会使用大量工具和材料来建造房屋,但最终的房屋在建造后不需要放置这些工具和多余的材料。

例子:

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

house = HouseBuilder.drills(drills).woodboards(woodboards).bricks(bricks).build()
Run Code Online (Sandbox Code Playgroud)