web-dev-qa-db-ja.com

オブジェクトのファクトリメソッド-ベストプラクティス?

これは、Pythonを使用して同じデータの異なる形式からクラスまたはタイプのインスタンスを作成するためのベストプラクティスに関する質問です。クラスメソッドを使用する方が良いですか、それとも別個の関数を完全に使用する方が良いですか?ドキュメントのサイズを説明するために使用されるクラスがあるとします。 (注:これは単なる例です。クラスのインスタンスを作成する最良の方法notがドキュメントのサイズを説明する最良の方法を知りたいです。)

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    def __init__(self, bits):
        self._bits = bits

    @property
    def bits(self):
        return float(self._bits)

    @property
    def bytes(self):
        return self.bits / self.BYTE

    @property
    def kilobits(self):
        return self.bits / self.KILO

    @property
    def kilobytes(self):
        return self.bytes / self.KILO

    @property
    def megabits(self):
        return self.kilobits / self.KILO

    @property
    def megabytes(self):
        return self.kilobytes / self.KILO

ぼくの __init__メソッドは、ビット(ビットおよびビットのみであり、そのように保持したい)で表されるサイズ値を受け取りますが、サイズ値がバイトであり、クラスのインスタンスを作成するとします。クラスメソッドを使用する方が良いですか、それとも別個の関数を完全に使用する方が良いですか?

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    @classmethod
    def from_bytes(cls, bytes):
        bits = bytes * cls.BYTE
        return cls(bits)

OR

def create_instance_from_bytes(bytes):
    bits = bytes * Size.BYTE
    return Size(bits)

これは問題のように見えないかもしれませんし、おそらく両方の例が有効ですが、私はこのようなものを実装する必要があるたびにそれについて考えます。長い間、クラスとファクトリメソッドを結びつけることの組織的な利点が好きなので、クラスメソッドアプローチを好んでいました。また、クラスメソッドを使用すると、サブクラスのインスタンスを作成する機能が保持されるため、よりオブジェクト指向になります。一方、「疑わしいときは標準ライブラリの機能を実行する」と友人がかつて言ったことがありますが、標準ライブラリでこれを示す例はまだありません。

23
Yani

まず、ほとんどの場合、このようなものが必要だと思いますが、必要はありません。これは、JavaのようにPython=を処理しようとしていることを示しています。解決策は、一歩下がって、なぜファクトリが必要なのかを尋ねることです。

多くの場合、最も簡単なことは、defaulted/optional/keyword引数を持つコンストラクターを用意することです。 Javaでそのように記述したことがない場合でも、オーバーロードされたコンストラクターがC++またはObjCで間違っていると感じる場合でも、Pythonでは完全に自然に見えるかもしれません。たとえば、size = Size(bytes=20)、またはsize = Size(20, Size.BYTES)は適切に見えます。さらに言えば、Sizeを継承し、___init___オーバーロード以外は何も追加しないBytes(20)クラスは合理的です。そして、これらを定義するのは簡単です:

_def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None):
_

または:

_BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object()
def __init__(self, count, unit=Size.BITS):
_

しかし、時にはdoファクトリ関数が必要です。それで、あなたはその後何をしますか?よく、2つの種類のものが一緒に「工場」にまとめられることがよくあります。

_@classmethod_は、「代替コンストラクタ」を実行する慣用的な方法です。stdlibには、_itertools.chain.from_iterable_、_datetime.datetime.fromordinal_などの例があります。

関数は、「実際のクラスが何であるかは気にしない」ファクトリーを実行する慣用的な方法です。たとえば、組み込みのopen関数を見てください。 3.3で何が返されるか知っていますか?手入れする?いいえ。それが_io.TextIOWrapper.open_などではなく関数である理由です。

あなたの与えられた例は完全に正当なユースケースのようであり、「代替コンストラクター」ビンにかなり明確に適合します(「追加の引数を持つコンストラクター」ビンに適合しない場合)。

27
abarnert