praktik terbaik fungsi pabrik python

30

Misalkan saya memiliki file yang foo.pyberisi kelas Foo:

class Foo(object):
   def __init__(self, data):
      ...

Sekarang saya ingin menambahkan fungsi yang membuat Fooobjek dengan cara tertentu dari data sumber mentah. Haruskah saya meletakkannya sebagai metode statis di Foo atau sebagai fungsi lain yang terpisah?

class Foo(object):
   def __init__(self, data):
      ...
# option 1:
   @staticmethod
   def fromSourceData(sourceData):
      return Foo(processData(sourceData))

# option 2:
def makeFoo(sourceData):
   return Foo(processData(sourceData))

Saya tidak tahu apakah lebih penting untuk nyaman bagi pengguna:

foo1 = foo.makeFoo(sourceData)

atau apakah lebih penting untuk mempertahankan kopling yang jelas antara metode dan kelas:

foo1 = foo.Foo.fromSourceData(sourceData)
Jason S
sumber

Jawaban:

45

Pilihan harus antara fungsi pabrik dan opsi ketiga sebagai gantinya; metode kelas:

class Foo(object):
   def __init__(self, data):
      ...

   @classmethod
   def fromSourceData(klass, sourceData):
      return klass(processData(sourceData))

Pabrik-pabrik metode memiliki keuntungan tambahan bahwa mereka dapat diwariskan. Anda sekarang dapat membuat subclass dari Foo, dan metode pabrik yang diwarisi kemudian akan menghasilkan instance dari subclass itu.

Dengan pilihan antara fungsi dan metode kelas, saya akan memilih metode kelas. Mendokumentasikan cukup jelas apa jenis objek pabrik akan memproduksi, tanpa harus membuat ini eksplisit dalam nama fungsi. Ini menjadi lebih jelas ketika Anda tidak hanya memiliki banyak metode pabrik, tetapi juga beberapa kelas.

Bandingkan dua alternatif berikut:

foo.Foo.fromFrobnar(...)
foo.Foo.fromSpamNEggs(...)
foo.Foo.someComputedVersion()
foo.Bar.fromFrobnar(...)
foo.Bar.someComputedVersion()

vs.

foo.createFooFromFrobnar()
foo.createFooFromSpamNEggs()
foo.createSomeComputedVersionFoo()
foo.createBarFromFrobnar()
foo.createSomeComputedVersionBar()

Lebih baik lagi, pengguna akhir Anda dapat mengimpor hanya Fookelas dan menggunakannya untuk berbagai cara Anda dapat membuatnya, karena Anda memiliki semua metode pabrik di satu tempat:

from foo import Foo
Foo()
Foo.fromFrobnar(...)
Foo.fromSpamNEggs(...)
Foo.someComputedVersion()

datetimeModul stdlib menggunakan pabrik metode kelas secara luas, dan API-nya jauh lebih jelas untuk itu.

Martijn Pieters
sumber
Jawaban yang bagus Untuk lebih lanjut tentang @classmethod, lihat Arti @classmethod dan @staticmethod untuk pemula?
nealmcb
Fokus hebat pada fungsionalitas pengguna akhir
drtf
1

Dengan Python, saya biasanya lebih suka hierarki kelas daripada metode pabrik. Sesuatu seperti:

class Foo(object):
    def __init__(self, data):
        pass

    def method(self, param):
        pass

class SpecialFoo(Foo):
    def __init__(self, param1, param2):
        # Some processing.
        super().__init__(data)

class FromFile(Foo):
    def __init__(self, path):
        # Some processing.
        super().__init__(data)

Saya akan menempatkan seluruh hierarki (dan hanya yang ini) dalam sebuah modul, katakanlah foos.py. Kelas dasar Foobiasanya mengimplementasikan semua metode (dan dengan demikian, antarmuka), dan biasanya tidak dibangun oleh pengguna secara langsung. Subclass adalah sarana untuk menimpa konstruktor dasar, dan menentukan bagaimana Fooharus dibangun. Lalu saya akan membuat Foodengan memanggil konstruktor yang sesuai, seperti:

foo1 = mypackage.foos.SpecialFoo(param1, param2)
foo2 = mypackage.foos.FromFile('/path/to/foo')

Saya merasa lebih elegan dan fleksibel daripada pabrik yang dibangun dari metode statis, metode kelas, atau fungsi tingkat modul.

mdeff
sumber