práticas recomendadas da função de fábrica python

30

Suponha que eu tenha um arquivo foo.pycontendo uma classe Foo:

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

Agora eu quero adicionar uma função que cria um Fooobjeto de uma certa maneira a partir de dados brutos de origem. Devo colocá-lo como um método estático no Foo ou como outra função 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))

Não sei se é mais importante ser conveniente para os usuários:

foo1 = foo.makeFoo(sourceData)

ou se é mais importante manter um acoplamento claro entre o método e a classe:

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

Respostas:

45

A escolha deve entre uma função de fábrica e uma terceira opção; o método da classe:

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

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

As fábricas do método de classe têm a vantagem adicional de serem herdadas. Agora você pode criar uma subclasse de Foo, e o método de fábrica herdado produzirá instâncias dessa subclasse.

Com uma escolha entre uma função e um método de classe, eu escolheria o método de classe. Ele documenta claramente que tipo de objeto a fábrica vai produzir, sem precisar explicitar isso no nome da função. Isso fica muito mais claro quando você não apenas possui vários métodos de fábrica, mas também várias classes.

Compare as duas alternativas a seguir:

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

Melhor ainda, seu usuário final pode importar apenas a Fooclasse e usá-la das várias maneiras que você pode criá-la, pois você tem todos os métodos de fábrica em um só lugar:

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

O datetimemódulo stdlib usa extensivamente fábricas de métodos de classe e sua API é muito mais clara para ele.

Martijn Pieters
fonte
Ótima resposta. Para @classmethodsaber mais , consulte Significado de @classmethod e @staticmethod para iniciantes?
N181 # nealmcb
Grande foco em funcionalidade para o usuário final
drtf
1

No Python, eu geralmente prefiro uma hierarquia de classes sobre os 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)

Vou colocar toda a hierarquia (e somente essa) em um módulo, digamos foos.py. A classe base Foogeralmente implementa todos os métodos (e, como tal, a interface) e geralmente não é construída diretamente pelos usuários. As subclasses são um meio de substituir o construtor de base e especificar como Foodeve ser construído. Então eu criaria um Foochamando o construtor apropriado, como:

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

Acho mais elegante e flexível do que fábricas construídas a partir de métodos estáticos, métodos de classe ou funções no nível de módulo.

mdeff
fonte