Эквивалент Builder в Python
В Java вы можете использовать шаблон конструктора, чтобы обеспечить более читаемое средство для создания экземпляра класса со многими параметрами. В шаблоне построителя создается объект конфигурации с методами для установки именованных атрибутов, а затем используется для построения другого объекта.
Что такое эквивалент в Python? Является ли лучший способ имитировать ту же реализацию?
Ответы
Ответ 1
Шаблоны проектирования часто могут быть заменены встроенными языковыми функциями.
Ваш прецедент
Вы говорите: "Я хотел иметь более читаемый" означает "создать экземпляр класса со многими параметрами". В случае Java:
[A] пример использования для шаблона построителя - это когда конструктор объекта, который должен быть построен, должен принимать очень много параметров. В таких случаях часто бывает удобно группировать такие параметры конфигурации в объекте-строителе (setMaxTemperature(int t)
, setMinTemperature(int t)
, set
.. и т.д.), Чем обременять вызывающего абонента длинным списком аргументов, конструктор класса..
Не требуется шаблон Builder
Но 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("Hello({})".format(i) for i in range(100))
Ответ 2
OP настроен на падение, отбросив шаблон Builder как специфичный для Java. Не это. Это в Gang of Four book и потенциально относится к любому объектно-ориентированному языку.
К сожалению, даже статья статьи Википедии о шаблоне Builder не дает ему достаточно кредитов. Это не просто полезно для элегантности кода. Шаблоны Builder - отличный способ создать неизменяемые объекты, которые необходимо изменить до тех пор, пока они не будут использованы. Непрерывное состояние особенно важно в функциональных парадигмах, что делает Builder отличным объектно-ориентированным шаблоном для python.
Ниже приведен пример реализации Builder + ImmutableObject, используя collections.namedtuple, заимствованный и измененный с "Как создать неизменяемый объект в python". Я сохранил Builder довольно просто. Тем не менее, могут быть предусмотрены функции setter, которые возвращают Builder сам, чтобы разрешить цепочку вызовов. Или синтаксис @property можно использовать в Builder для предоставления адаптеров атрибутов, проверяющих достоверность атрибута до установки.
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, все с разными параметрами. Я предоставил вызывающей программе возможность копировать, изменять и передавать измененную конфигурацию в форме строителя, сохраняя при этом неизменность встроенных объектов. Установка и удаление атрибутов в объектах ImmutableObject вызывает ошибки.
Итог: Builders - отличный способ передать что-то с изменчивым состоянием, которое предоставляет объект с неизменяемым состоянием, когда вы будете готовы его использовать. Или, по-разному, Builders - отличный способ предоставить установщики атрибутов, сохраняя при этом неизменное состояние. Это особенно ценно в функциональных парадигмах.
Ответ 3
Я не согласен с @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()
Ответ 4
Шаблон построителя в Java может быть легко реализован в python с помощью варианта:
MyClass(self, required=True, someNumber=<default>, *args, **kwargs)
где required
и someNumber
- пример, показывающий требуемые параметры со значением по умолчанию, а затем чтение для переменных аргументов при обработке случая, когда может быть None
Если вы ранее не использовали переменные аргументы, обратитесь к this