web-dev-qa-db-ja.com

Python

Javaでは、 builder pattern を使用して、多くのパラメーターを持つクラスをインスタンス化するためのより読みやすい手段を提供できます。ビルダーパターンでは、名前付き属性を設定するメソッドを持つ構成オブジェクトを作成し、それを使用して別のオブジェクトを作成します。

Pythonで同等のものは何ですか?同じ実装を模倣する最良の方法はありますか?

44
name_masked

多くの場合、設計パターンは組み込みの言語機能に置き換えることができます。

ユースケース

「多くのパラメーターを使用してクラスをインスタンス化するための、より読みやすい「手段」が必要でした」と言います。 Javaの場合:

[A]ビルダーパターンのユースケースは、構築するオブジェクトのコンストラクターが非常に多くのパラメーターを取得する必要がある場合です。このような場合、そのような構成パラメーターをビルダーオブジェクトにまとめておくと便利です(setMaxTemperature(int t)setMinTemperature(int t)set ..など)を呼び出し元に渡すために、クラスのコンストラクタに渡す引数の長いリストを使用します。

ビルダーパターンは不要

しかし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 Buildersと同様の方法で使用できます。これを提案してくれた 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))
_
87

OPは、ビルダーパターンをJava固有。キャストしません。それはそうではありません。 Gang of Fourの本 にあり、潜在的にどのオブジェクトにも関連します。指向言語。

残念ながら、 Builderパターンに関するウィキペディアの記事 でさえ、十分な信用を与えていません。コードの優雅さのためだけに便利ではありません。 Builderパターンは、使用するまで変更可能である必要がある不変オブジェクトを作成するための優れた方法です。不変状態は、機能パラダイムにおいて特に重要であり、ビルダーPython用の優れたオブジェクト指向パターン。

collections.namedtuple を使用して、以下の例のBuilder + ImmutableObject実装を提供しました。借りて、「 pythonで不変オブジェクトを作成する方法 」から変更しました。 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')])"

この例では、すべて異なるパラメーターを持つ3つのImmutableObjectを作成しました。呼び出し元に、ビルドされたオブジェクトの不変性を保証しながら、ビルダーの形で可変構成をコピー、変更、および渡す機能を与えました。 ImmutableObjectsで属性を設定および削除すると、エラーが発生します。

結論:ビルダーは、オブジェクトを使用する準備が整ったときに不変の状態をオブジェクトに提供する、可変状態の何かを渡す素晴らしい方法です。または、別の言い方をすれば、ビルダーは、不変の状態を維持しながら属性セッターを提供する優れた方法です。これは、機能的なパラダイムで特に価値があります。

34
Malina

@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()
13
CowboyBebop

Javaのビルダーパターンは、pythonで簡単に実現できます。

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

ここで、requiredsomeNumberは、デフォルト値で必要なパラメーターを表示し、Noneが存在する可能性のあるケースを処理しながら変数引数を読み取る例です。

以前に変数引数を使用したことがない場合は、 this を参照してください

4