Ao projetar um sistema, sou frequentemente confrontado com o problema de ter vários módulos (registro, acesso ao banco de dados, etc.) sendo usados pelos outros módulos. A questão é: como faço para fornecer esses componentes a outros componentes. Duas respostas parecem possíveis injeção de dependência ou usando o padrão de fábrica. No entanto, ambos parecem errados:
- As fábricas tornam o teste uma tarefa difícil e não permitem a troca fácil de implementações. Eles também não tornam as dependências aparentes (por exemplo, você está examinando um método, alheio ao fato de que ele chama um método que chama um método que chama um método que usa um banco de dados).
- A injeção de dependência aumenta massivamente as listas de argumentos do construtor e mancha alguns aspectos em todo o seu código. Situação típica é onde construtores de mais da metade das classes se parecem com isso
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Aqui está uma situação típica com a qual tenho um problema: Tenho classes de exceção, que usam descrições de erro carregadas do banco de dados, usando uma consulta com parâmetro de configuração do idioma do usuário, que está no objeto de sessão do usuário. Portanto, para criar uma nova exceção, preciso de uma descrição, que exija uma sessão de banco de dados e a sessão do usuário. Portanto, estou fadado a arrastar todos esses objetos em todos os meus métodos para o caso de eu precisar lançar uma exceção.
Como faço para resolver esse problema?
Respostas:
Use injeção de dependência, mas sempre que as listas de argumentos do construtor se tornarem muito grandes, refatore-a usando um Serviço de fachada . A idéia é agrupar alguns dos argumentos do construtor, introduzindo uma nova abstração.
Por exemplo, você pode introduzir um novo tipo que
SessionEnvironment
encapsule aDBSessionProvider
, theUserSession
e o carregadoDescriptions
. Para saber quais abstrações fazem mais sentido, no entanto, é preciso conhecer os detalhes do seu programa.Uma pergunta semelhante já foi feita aqui no SO .
fonte
A partir disso, não parece que você entenda o DI adequadamente - a idéia é inverter o padrão de instanciação de objetos dentro de uma fábrica.
Seu problema específico parece ser um problema mais geral de POO. Por que os objetos não podem simplesmente lançar exceções normais e não legíveis por humanos durante o tempo de execução e depois ter algo antes da tentativa / captura final que captura essa exceção e, nesse ponto, usa as informações da sessão para lançar uma nova exceção mais bonita ?
Outra abordagem seria ter uma fábrica de exceções, que é passada aos objetos por meio de seus construtores. Ao invés de jogar uma nova exceção, a classe pode jogar em um método de fábrica (por exemplo
throw PrettyExceptionFactory.createException(data)
.Lembre-se de que seus objetos, além dos objetos de fábrica, nunca devem usar o
new
operador. As exceções geralmente são o único caso especial, mas no seu caso elas podem ser uma exceção!fonte
Builder
padrão) dita. Se você está passando parâmetros para o construtor porque seu objeto instancia outros objetos, esse é um caso claro para a IoC.Você já listou muito bem as desvantagens do padrão estático de fábrica, mas não concordo totalmente com as desvantagens do padrão de injeção de dependência:
Essa injeção de dependência exige que você escreva o código para cada dependência não é um bug, mas um recurso: obriga você a pensar se realmente precisa dessas dependências, promovendo assim um acoplamento flexível. No seu exemplo:
Não, você não está condenado. Por que é responsabilidade da lógica comercial localizar suas mensagens de erro para uma sessão de usuário específica? E se, em algum momento no futuro, você quiser usar esse serviço comercial de um programa em lote (que não possui uma sessão do usuário ...)? Ou, se a mensagem de erro não for exibida ao usuário conectado no momento, mas ao seu supervisor (que pode preferir um idioma diferente)? Ou, se você quiser reutilizar a lógica de negócios no cliente (que não tem acesso a um banco de dados ...)?
Claramente, a localização de mensagens depende de quem as vê, ou seja, é de responsabilidade da camada de apresentação. Portanto, eu lançaria exceções comuns do serviço comercial, que carregam um identificador de mensagem que pode ser consultado no manipulador de exceção da camada de apresentação em qualquer fonte de mensagem que ele usar.
Dessa forma, você pode remover três dependências desnecessárias (UserSession, ExceptionFactory e provavelmente descrições), tornando seu código mais simples e versátil.
De um modo geral, eu usaria apenas fábricas estáticas para coisas às quais você precisa de acesso onipresente e que estão garantidas em todos os ambientes nos quais poderíamos querer executar o código (como Log). Para todo o resto, eu usaria injeção de dependência antiga simples.
fonte
Use injeção de dependência. Usar fábricas estáticas é um emprego do
Service Locator
antipadrão. Veja o trabalho seminal de Martin Fowler aqui - http://martinfowler.com/articles/injection.htmlSe seus argumentos de construtor se tornarem muito grandes e você não estiver usando um contêiner de DI, escreva suas próprias fábricas para instanciação, permitindo que seja configurável, seja por XML ou vinculando uma implementação a uma interface.
fonte
Eu também aceitaria a injeção de dependência. Lembre-se de que o DI não é feito apenas por meio de construtores, mas também por meio de configuradores de propriedades. Por exemplo, o criador de logs pode ser injetado como uma propriedade.
Além disso, convém usar um contêiner de IoC que possa elevar parte da carga para você, por exemplo, mantendo os parâmetros do construtor nas coisas necessárias em tempo de execução pela lógica do seu domínio (mantendo o construtor de uma maneira que revele a intenção de a classe e as dependências do domínio real) e talvez injetar outras classes auxiliares por meio de propriedades.
Um passo adiante que você pode querer dar é o Programa Orientado a Aspectos, que é implementado em muitas estruturas principais. Isso pode permitir que você intercepte (ou "aconselhe" a usar a terminologia AspectJ) o construtor da classe e injete as propriedades relevantes, talvez com um atributo especial.
fonte
Eu não concordo completamente. Pelo menos não em geral.
Fábrica simples:
Injeção simples:
Ambos os snippets têm o mesmo objetivo, eles estabelecem um link entre
IFoo
eFoo
. Tudo o resto é apenas sintaxe.Alterar
Foo
paraADifferentFoo
exige exatamente o mesmo esforço em qualquer exemplo de código.Ouvi pessoas argumentarem que a injeção de dependência permite que diferentes ligações sejam usadas, mas o mesmo argumento pode ser feito sobre a fabricação de fábricas diferentes. Escolher a encadernação correta é exatamente tão complexo quanto escolher a fábrica certa.
Os métodos de fábrica permitem que você, por exemplo, use
Foo
em alguns lugares eADifferentFoo
em outros lugares. Alguns podem chamar isso de bom (útil se você precisar), outros podem chamar isso de ruim (você pode fazer um trabalho de meia-boca para substituir tudo).No entanto, não é tão difícil evitar essa ambiguidade se você se ater a um único método que retorna
IFoo
para que você sempre tenha uma única fonte. Se você não quer dar um tiro no pé, não segure uma arma carregada ou não a aponte para o pé.É por isso que algumas pessoas preferem recuperar explicitamente dependências no construtor, assim:
Eu ouvi argumentos pro (nenhum construtor inchaço), eu ouvi argumentos con (usar o construtor permite mais automação de DI).
Pessoalmente, enquanto eu cedi ao nosso veterano que deseja usar argumentos construtores, notei um problema com a lista suspensa no VS (canto superior direito, para navegar pelos métodos da classe atual), onde os nomes dos métodos desaparecem quando um das assinaturas do método é maior que minha tela (=> o construtor inchado).
No nível técnico, eu não me importo de nenhuma maneira. Qualquer uma das opções requer o mesmo esforço para digitar. E como você está usando DI, normalmente não chama um construtor manualmente de qualquer maneira. Mas o bug da interface do usuário do Visual Studio me faz favor não inchar o argumento do construtor.
Como uma observação lateral, injeção de dependência e fábricas não são mutuamente exclusivas . Já tive casos em que, em vez de inserir uma dependência, inseri uma fábrica que gera dependências (NInject felizmente permite que você use,
Func<IFoo>
para que você não precise criar uma classe de fábrica real).Os casos de uso para isso são raros, mas eles existem.
fonte
Neste exemplo simulado, uma classe de fábrica é usada em tempo de execução para determinar qual tipo de objeto de solicitação HTTP de entrada instanciar, com base no método de solicitação HTTP. A própria fábrica é injetada com uma instância de um contêiner de injeção de dependência. Isso permite que a fábrica faça sua determinação de tempo de execução e permita que o contêiner de injeção de dependência lide com as dependências. Cada objeto de solicitação HTTP de entrada possui pelo menos quatro dependências (superglobais e outros objetos).
O código do cliente para uma configuração do tipo MVC, dentro de uma centralização
index.php
, pode se parecer com o seguinte (validações omitidas).Como alternativa, você pode remover a natureza estática (e a palavra-chave) da fábrica e permitir que um injetor de dependência gerencie tudo (portanto, o construtor comentado). No entanto, você precisará alterar algumas (não as constantes) das referências dos membros da classe (
self
) para os membros da instância ($this
).fonte