лучшие практики работы фабрики 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))

Фабрики Classmethod имеют дополнительное преимущество в том, что они наследуются. Теперь вы можете создать подкласс Foo, и унаследованный метод фабрики будет производить экземпляры этого подкласса.

Имея выбор между функцией и методом класса, я бы выбрал метод класса. Это документы совершенно ясно , что вид объекта завод будет производить, без того , чтобы сделать это явно в имени функции. Это становится намного понятнее, когда у вас есть не только несколько фабричных методов, но и несколько классов.

Сравните следующие две альтернативы:

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

против

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

Я нахожу его более элегантным и гибким, чем фабрики, построенные из статических методов, методов класса или функций уровня модуля.

mdeff
источник