Передовий досвід роботи фабрики python


30

Припустимо, у мене є файл, foo.pyщо містить клас Foo:

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

Тепер я хочу додати функцію, яка Fooпевним чином створює об’єкт із необроблених вихідних даних. Чи слід ставити його як статичний метод у Foo або як іншу окрему функцію?

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))

Я не знаю, чи важливіше бути зручним для користувачів:

foo1 = foo.makeFoo(sourceData)

чи важливіше підтримувати чітке сполучення між методом та класом:

foo1 = foo.Foo.fromSourceData(sourceData)

Відповіді:


45

Натомість вибір повинен бути між заводською функцією та третім варіантом; метод класу:

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

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

Фабрики класових методів мають додаткову перевагу в тому, що вони є спадковими. Тепер ви можете створити підклас Foo, і після цього успадкований заводський метод створить екземпляри цього підкласу.

Вибираючи функцію та метод класу, я вибрав би метод класу. Це документи абсолютно ясно , що вид об'єкта завод буде виробляти, без того , щоб зробити це явно в імені функції. Це стає набагато зрозумілішим, коли у вас є не лише кілька заводських методів, але й кілька класів.

Порівняйте наступні два варіанти:

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()

Ще краще, що ваш кінцевий користувач може імпортувати лише Fooклас та використовувати його для різних способів його створення, оскільки у вас є всі заводські методи в одному місці:

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

STDLIB datetimeмодуль використовує метод класу фабрики широко, і це API набагато ясніше для нього.


Чудова відповідь. Більше про це @classmethodдив. У розділі Значення @classmethod та @staticmethod для початківців?
nealmcb

Великий фокус на функціональності кінцевих користувачів
drtf

1

У Python я, як правило, віддаю перевагу ієрархії класів над фабричними методами. Щось на зразок:

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)

Я поставлю всю ієрархію (і лише цю) в модуль, скажімо foos.py. Базовий клас Fooзазвичай реалізує всі методи (і як такий інтерфейс), і зазвичай не будується користувачами безпосередньо. Підкласи - це засіб перезапису базового конструктора та вказівка ​​способу Fooпобудови. Тоді я б створив Foo, зателефонувавши до відповідного конструктора, наприклад:

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

Я вважаю його більш елегантним та гнучким, ніж фабрики, побудовані з статичних методів, методів класів або функцій на рівні модулів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.