Como usar a injeção de dependência em conjunto com o padrão de fábrica

11

Considere um módulo que é responsável por analisar arquivos de qualquer tipo. Estou pensando em usar o padrão de estratégia para resolver esse problema, como já expliquei aqui . Consulte a publicação vinculada antes de prosseguir com esta pergunta.

Considere a classe B que precisa do conteúdo do arquivo product.xml. Essa classe precisará instanciar o implementador concreto apropriado da interface Parser para analisar o arquivo XML. Posso delegar a instanciação do implementador de concreto apropriado a uma fábrica, de modo que a classe B "tenha uma fábrica". No entanto, a classe B "dependerá" de uma fábrica para instanciar o implementador de concreto. Isso significa que o método construtor ou setter na classe B precisará ser aprovado na fábrica.

Portanto, o Factory e a classe B que precisam analisar um arquivo serão fortemente acoplados. Entendo que posso estar completamente errado sobre o que expliquei até agora. Gostaria de saber se posso usar a injeção de dependência em um cenário em que a dependência a ser injetada é uma Fábrica e qual seria a maneira correta de implementar isso para que eu possa tirar proveito de áreas como zombar da Fábrica nos testes de unidade.

CKing
fonte

Respostas:

8

A maneira correta de fazer isso é depender de uma interface e injetar uma implementação dessa interface na Classe B.

Uma interface é a coisa mais fina em que você pode confiar - eu comparo isso a tentar pegar uma mecha de fumaça. O código precisa acoplar-se a alguma coisa ou não fará nada, mas o acoplamento a uma interface é o mais dissociado possível, mas as interfaces podem fornecer toda a funcionalidade que você deseja.

Portanto, faça com que o construtor da Classe B use uma interface com a classe de que precisa e faça com que a fábrica produza essa classe como implementadora da interface. Não dependa da fábrica, dependa da interface e faça com que a fábrica forneça uma implementação dessa fábrica.

Então, sim, você estará usando injeção de dependência, mas não há nada de errado nisso. Injeção de dependência - injeção de construtor particularmente simples - deve ser a maneira "normal" de fazer as coisas. Simplesmente adie o que há de mais novo em seu aplicativo (e o mais próximo possível da primeira linha de main) e oculte a nova chamada em uma classe projetada especificamente para criar coisas.

Conclusão: não hesite em injetar dependências. Essa deve ser a maneira normal de fazer as coisas.

Nick Hodges
fonte
A injeção de construtores adia coisas novas o maior tempo possível?
22413 JeffO
Foi mal colocado - corrigi-o para dizer "o mais longe possível no seu aplicativo".
Nick Hodges
Sim. Eu estava pensando em depender da interface do Parser em vez de uma fábrica. Agora, se outra classe usar a classe B, ela terá que depender da interface da qual a classe B depende. Isso continuará até a classe de nível superior. Esta classe de nível superior precisará finalmente usar uma fábrica para instanciar a instância concreta apropriada do Analisador. A classe de nível superior deve depender de uma fábrica ou deve usá-la diretamente dentro de seus métodos?
CKing 23/02
1
Bem disse: não hesitar em dependências Inject
Signcodeindie
9

Eu acho que sua premissa é um pouco confusa aqui, você fala em injetar uma fábrica, mas o padrão de fábrica é um padrão criacional cujo objetivo era fazer um subconjunto do que uma estrutura de injeção de dependência faz, quando as estruturas de DI não eram predominantes. útil por esse motivo. No entanto, se você tiver uma estrutura de DI, não precisará mais de uma fábrica, pois ela pode cumprir o propósito que a fábrica teria cumprido.

Dito isto, deixe-me explicar um pouco sobre a injeção de dependência e como você geralmente a usaria.

Existem várias maneiras de fazer injeção de dependência, mas as mais comuns são injeção de construtor, injeção de propriedade e DIContainer direto. Vou falar sobre a injeção de construtores, pois a injeção de propriedades é a abordagem errada na maioria das vezes (a abordagem correta algumas vezes), e o acesso ao DIContainer não é preferível, exceto quando você absolutamente não pode fazer nenhuma das outras abordagens.

A injeção de construtor é onde você tem a interface para uma dependência e um DIContainer (ou fábrica) que conhece a implementação concreta dessa dependência e onde quer que você precise de um objeto que depende dessa interface, no momento da construção, você entrega a implementação da fábrica para isto.

ie

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

Muitas estruturas de DI podem simplificá-lo significativamente para onde o DIContainer inspecionará o construtor do UserRepository em busca de interfaces para as quais ele conhece implementações concretas, e automaticamente as entregará para você; Essa técnica é freqüentemente chamada de Inversão de controle, embora DI e IoC sejam termos que se intercambiam muito e têm diferenças vagas (se houver).

Agora, se você está se perguntando como o código abrangente acessa o DIContainer, bem, você pode ter uma classe estática para acessá-lo ou o mais apropriado é que a maioria das estruturas de DI permita que você crie um DIContainer, em que ele realmente se comportará como um wrapper para um dicionário singleton interno para os tipos que ele sabe serem concretos para determinadas interfaces.

Isso significa que você pode atualizar o DIContainer em qualquer lugar que desejar no código e obter efetivamente o mesmo DIContainer que você já configurou para conhecer seus relacionamentos entre interface e concreto. Um meio usual de ocultar o DIContainer de partes do código que não devem interagir diretamente com ele é simplesmente garantir que apenas os projetos necessários tenham uma referência à estrutura de DI.

Jimmy Hoffa
fonte
Não concordo plenamente com o primeiro parágrafo. Ainda existem cenários em que você depende de uma fábrica (interface de implementação) que é injetada pelo contêiner de DI.
Piotr Perak
Acho que você não entendeu o argumento, já que o OP não falou sobre estruturas de DI ou contêineres de DI.
Doc Brown
@ Jimmy Hoffa Enquanto você fez alguns pontos muito interessantes, conheço os contêineres IoC, como o Spring, e o conceito de injeção de dependência. Minha preocupação era com relação a um cenário em que você não usa uma estrutura de IoC; nesse caso, você precisa escrever uma classe que instanciará suas dependências para você. Se a classe A depende da interface B e a classe C usa a classe A, a classe C depende da interface B. Alguém precisa atribuir à Classe C uma classe B ce. A classe C deve depender de cla
CKing
Se a classe A depende da interface B e a classe C usa a classe A, a classe C depende da interface B. Alguém precisa fornecer uma interface à Classe C B. A classe C deve depender da interface B ou deve depender da classe que instanciaram o dependências Ou seja, a fábrica. Você parece ter respondido a esta pergunta na sua resposta, mas com uma sobrecarga de informações :) #
226
@bot como eu disse no prefácio, em grande parte você pode tratar as fábricas como um subconjunto da funcionalidade de um contêiner de DI, não é verdade em todos os cenários, como o Dr. Brown mencionou, mas olhe para o meu exemplo de código e substitua DIContainer- DbConnectionFactoryo pelo conceito ainda se espera que você recupere a implementação concreta do seu DI / Factory / etc e a entregue aos consumidores do tipo no momento da construção.
Jimmy Hoffa
5

Você pode passar por uma fábrica por injeção de dependência, assim como você passa por qualquer outra coisa, não deixe que a recursividade da situação o confunda. Não sei mais o que dizer sobre a implementação - você já sabe como fazer injeção de dependência.

Eu uso o DI para injetar fábricas com bastante regularidade.

psr
fonte
1
Se uma classe depende de uma fábrica, você precisará zombar da fábrica ao testar a unidade. Como você faz isso? Deixar a classe depender de uma interface de fábrica?
CKing 23/02
4

Não há nada errado em injetar fábricas. É uma maneira padrão se você não puder decidir durante a construção do objeto 'pai' que tipo de dependência será necessária. Eu acho que o exemplo vai explicar melhor. Vou usar c # porque não conheço java.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Piotr Perak
fonte