mejores prácticas de la función de fábrica de Python

30

Supongamos que tengo un archivo que foo.pycontiene una clase Foo:

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

Ahora quiero agregar una función que cree un Fooobjeto de cierta manera a partir de datos de origen sin procesar. ¿Debo ponerlo como un método estático en Foo o como otra función separada?

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

No sé si es más importante ser conveniente para los usuarios:

foo1 = foo.makeFoo(sourceData)

o si es más importante mantener un acoplamiento claro entre el método y la clase:

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

Respuestas:

45

La elección debería ser entre una función de fábrica y una tercera opción; El método de clase:

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

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

Las fábricas de método de clase tienen la ventaja adicional de que son heredables. Ahora puede crear una subclase de Foo, y el método de fábrica heredado produciría instancias de esa subclase.

Con una elección entre una función y un método de clase, elegiría el método de clase. Documenta con bastante claridad qué tipo de objeto va a producir la fábrica, sin tener que hacer esto explícito en el nombre de la función. Esto se vuelve mucho más claro cuando no solo tiene múltiples métodos de fábrica, sino también múltiples clases.

Compare las siguientes dos alternativas:

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

Aún mejor, su usuario final podría importar solo la Fooclase y usarla para las diversas formas en que puede crearla, ya que tiene todos los métodos de fábrica en un solo lugar:

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

El datetimemódulo stdlib utiliza ampliamente las fábricas de métodos de clase, y su API es mucho más clara.

Martijn Pieters
fuente
Gran respuesta. Para obtener más información @classmethod, consulte Significado de @classmethod y @staticmethod para principiantes.
nealmcb
Gran enfoque en la funcionalidad del usuario final
drtf
1

En Python, generalmente prefiero una jerarquía de clases sobre los métodos de fábrica. Algo como:

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)

Pondré toda la jerarquía (y solo esta) en un módulo, digamos foos.py. La clase base Foogeneralmente implementa todos los métodos (y como tal, la interfaz), y generalmente no es construida por los usuarios directamente. Las subclases son un medio para sobrescribir el constructor base y especificar cómo se Foova a construir. Luego crearía un Foollamando al constructor apropiado, como:

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

Me parece más elegante y flexible que las fábricas construidas a partir de métodos estáticos, métodos de clase o funciones de nivel de módulo.

mdeff
fuente