Classes base como fábricas?

14

Eu estava escrevendo algum código no fim de semana e me vi querendo escrever uma fábrica como um método estático em uma classe base.

Minha pergunta é simplesmente saber se esta é uma abordagem ac # idomatic?

Minha sensação de que pode não ser deriva do fato de a classe base ter conhecimento da classe derivada.

Dito isto, não tenho certeza de uma maneira mais simples de obter o mesmo resultado. Toda uma outra classe de fábrica parece (pelo menos para mim) uma complexidade desnecessária (?)

Algo como:

class Animal
{
  public static Animal CreateAnimal(string name)
  {
     switch(name)
     {
        case "Shark":
          return new SeaAnimal();
          break;
        case "Dog":
          return new LandAnimal();
          break;
        default:
          throw new Exception("unknown animal");
     }
  }
}
class LandAnimal : Animal
{
}

class SeaAnimal : Animal
{
}
Anodeto de Aaron
fonte
Como você testará suas fábricas?
com o código nesta pergunta, eu não faria. mas a resposta ajudou-me ao longo do caminho de integrar mais testes ao meu estilo de codificação
Aaron Anodide
Considere que, tendo puxado tanto o Mundo Marinho quanto o Mundo da Terra na sua classe Animal, tudo torna o manuseio mais difícil em um ambiente isolado.

Respostas:

13

Bem, a vantagem de uma classe de fábrica separada é que ela pode ser ridicularizada em testes de unidade.

Mas se você não fizer isso ou torná-lo polimórfico de qualquer outra maneira, um método estático de Factory na própria classe é bom.

pdr
fonte
obrigado, você poderia dar um exemplo do que você está se referindo quando diz polimórfico de outra maneira?
Aaron Anodide
Quero dizer que, se você quiser substituir um método de fábrica por outro. Zombar para fins de teste é apenas um dos exemplos mais comuns disso.
Pd #
18

Você pode usar Generics para evitar a instrução switch e desacoplar as implementações downstream da classe base também:

 public static T CreateAnimal<T>() where T: new, Animal
 {
    return new T();
 }

Uso:

LandAnimal rabbit = Animal.CreateAnimal();  //Type inference should should just figure out the T by the return indicated.

ou

 var rabbit = Animal.CreateAnimal<LandAnimal>(); 
Nick Nieslanik
fonte
2
@PeterK. Fowler se refere a instruções de chave no Code Smells, mas sua solução é que elas sejam extraídas para as classes Factory, em vez de serem substituídas. Dito isto, se você sempre souber o tipo no momento do desenvolvimento, os genéricos são uma solução ainda melhor. Mas se você não o fizer (como o exemplo original implicava), eu argumentaria que usar a reflexão para evitar uma mudança no método de fábrica pode ser uma economia falsa.
Pdr5
As instruções switch e as cadeias if-else-if são iguais. i usar o antigo por causa da verificação de tempo de compilação que as forças o caso padrão para ser tratado
Aaron Anodide
4
@ Peter, em uma instrução switch, o código está em um único local. No OO completo, o mesmo código pode ser espalhado por várias classes separadas. Há uma área cinzenta em que a complexidade adicional de ter vários trechos é menos desejável do que uma instrução switch.
@Thorbjorn, eu tive esse pensamento exato, mas nunca tão sucintamente, obrigado por colocar em palavras
Aaron Anodide
5

Levada ao extremo, a fábrica também pode ser genérica.

interface IFactory<K, T> where K : IComparable
{
    T Create(K key);
}

Em seguida, pode-se criar qualquer tipo de fábrica de objetos, que, por sua vez, podem criar qualquer tipo de objeto. Não sei se isso é mais simples, é certamente mais genérico.

Não vejo nada de errado com uma instrução switch para uma pequena implementação de fábrica. Quando você estiver em um grande número de objetos ou em possíveis hierarquias de classe diferentes de objetos, acho que uma abordagem mais genérica é mais adequada.

Jon Raynor
fonte
1

Péssima ideia. Primeiro, viola o princípio de aberto-fechado. Para qualquer novo animal, você teria que mexer com sua classe base novamente e possivelmente a quebraria. As dependências seguiriam o caminho errado.

Se você precisar criar animais a partir de uma configuração, uma construção como essa seria uma espécie de OK, embora usar reflexão para obter o tipo que corresponde ao nome e instancia-lo usando as informações de tipo obtidas seria uma opção melhor.

Mas você deve criar uma classe de fábrica dedicada de qualquer maneira, desatada da hierarquia de classes de animais e retornar uma interface IAnimal em vez de um tipo base. Então isso se tornaria útil, você teria conseguido alguma dissociação.

Martin Maat
fonte
0

Esta pergunta é oportuna para mim - eu estava escrevendo quase exatamente esse código ontem. Apenas substitua "Animal" pelo que for relevante em meu projeto, embora eu continue com "Animal" para fins de discussão aqui. Em vez de uma declaração 'switch', eu tinha uma série um pouco mais complexa de declarações 'if', envolvendo mais do que apenas comparar uma variável a certos valores fixos. Mas isso é um detalhe. Um método estático de fábrica parecia uma maneira elegante de projetar coisas, pois o design surgiu da refatoração de bagunças de código rápidas e sujas anteriores.

Rejeitei esse design com base na classe base que tinha conhecimento da classe derivada. Se as classes LandAnimal e SeaAnimal forem pequenas, organizadas e fáceis, elas poderão estar no mesmo arquivo de origem. Mas tenho grandes métodos confusos para ler arquivos de texto que não estão em conformidade com nenhum padrão oficialmente definido - quero minha classe LandAnimal em seu próprio arquivo de origem.

Isso leva à dependência do arquivo circular - o LandAnimal é derivado de Animal, mas o Animal precisa saber que LandAnimal, SeaAnimal e quinze outras classes existem. Peguei o método de fábrica, coloquei em seu próprio arquivo (no meu aplicativo principal, não na minha biblioteca Animals) Ter um método de fábrica estático parecia bonito e inteligente, mas percebi que ele realmente não resolveu nenhum problema de design.

Não tenho idéia de como isso se relaciona com o C # idiomático, já que mudo muito de idioma, geralmente ignoro idiomas e convenções peculiares a idiomas e pilhas de desenvolvimento fora do meu trabalho habitual. Se alguma coisa meu C # pode parecer "pitônico", se isso é significativo. Eu busco clareza geral.

Além disso, não sei se havia uma vantagem em usar o método estático de fábrica no caso de classes pequenas e simples que provavelmente não serão estendidas em trabalhos futuros - ter tudo em um arquivo de origem pode ser bom em alguns casos.

DarenW
fonte