Recipientes do COI quebram os princípios da OOP

51

Qual é o objetivo dos contêineres do COI? Os motivos combinados para isso podem ser simplificados para o seguinte:

Ao usar os princípios de desenvolvimento OOP / SOLID, a injeção de dependência fica confusa. Você tem os pontos de entrada de nível superior gerenciando dependências para vários níveis abaixo de si mesmos e passando dependências recursivamente através da construção, ou possui um código duplicado nos padrões de fábrica / construtor e interfaces que constroem dependências conforme necessário.

Não existe uma maneira OOP / SOLID de executar isso E possui um código super bonito.

Se a declaração anterior for verdadeira, como os contêineres do IOC o fazem? Até onde eu sei, eles não estão empregando alguma técnica desconhecida que não pode ser feita com o DI manual. Portanto, a única explicação é que os contêineres do COI quebram os princípios OOP / SOLID usando acessadores privados de objetos estáticos.

Os contêineres do COI quebram os seguintes princípios nos bastidores? Esta é a verdadeira questão, pois tenho um bom entendimento, mas sinto que alguém tem um entendimento melhor:

  1. Controle de escopo . O escopo é o motivo de quase todas as decisões que tomo no meu design de código. Bloco, Local, Módulo, Estático / Global. O escopo deve ser muito explícito, tanto no nível de bloco quanto no mínimo de Static possível. Você deve ver declarações, instanciações e finais do ciclo de vida. Confio na linguagem e no GC para gerenciar o escopo, desde que explícito. Em minha pesquisa, descobri que os contêineres do COI configuram a maioria ou todas as dependências como estáticas e as controlam através de alguma implementação da AOP nos bastidores. Então, nada é transparente.
  2. Encapsulamento . Qual é o propósito do encapsulamento? Por que devemos manter os membros privados? Por razões práticas, os implementadores de nossa API não podem interromper a implementação alterando o estado (que deve ser gerenciado pela classe do proprietário). Mas também, por razões de segurança, é possível que não ocorram injeções que ultrapassem nossos estados membros e ignorem a validação e o controle de classe. Portanto, qualquer coisa (estruturas de zombaria ou estruturas de COI) que de alguma forma injeta código antes do tempo de compilação para permitir acesso externo a membros privados é bastante grande.
  3. Princípio de responsabilidade única . Na superfície, os contêineres do COI parecem tornar as coisas mais limpas. Mas imagine como você faria a mesma coisa sem as estruturas auxiliares. Você teria construtores com uma dúzia de dependências sendo passadas. Isso não significa encobrir os contêineres do COI, é uma coisa boa! É um sinal para re-fatorar seu código e seguir o SRP.
  4. Aberto / Fechado . Assim como o SRP não é somente de classe (aplico o SRP a linhas de responsabilidade única, quanto mais a métodos). Aberto / Fechado não é apenas uma teoria de alto nível para não alterar o código de uma classe. É uma prática de entender a configuração do seu programa e ter controle sobre o que é alterado e o que é estendido. Os contêineres do COI podem mudar a maneira como suas classes funcionam parcialmente porque:

    • uma. O código principal não está determinando a mudança de dependências, é a configuração da estrutura.

    • b. O escopo pode ser alterado em um momento que não é controlado pelos membros que chamam, é determinado externamente por uma estrutura estática.

Portanto, a configuração da classe não está realmente fechada, ela se altera com base na configuração de uma ferramenta de terceiros.

A razão pela qual essa é uma pergunta é porque eu não sou necessariamente um mestre de todos os contêineres do COI. E embora a ideia de um contêiner do COI seja boa, eles parecem ser apenas uma fachada que cobre uma implementação ruim. Mas, para obter controle externo do escopo, acesso de membros privados e carregamento lento, muitas coisas questionáveis ​​não triviais precisam continuar. AOP é excelente, mas a maneira como é realizada por meio de contêineres do COI também é questionável.

Posso confiar no C # e no .NET GC para fazer o que eu espero. Mas não posso confiar nessa mesma ferramenta de terceiros que está alterando meu código compilado para executar soluções alternativas para coisas que eu não seria capaz de fazer manualmente.

EG: O Entity Framework e outros ORMs criam objetos fortemente tipados e os mapeiam para entidades de banco de dados, além de fornecer funcionalidades básicas para executar CRUD. Qualquer um poderia criar seu próprio ORM e continuar seguindo os Princípios OOP / SOLID manualmente. Mas essas estruturas nos ajudam a não ter que reinventar a roda todas as vezes. Enquanto os contêineres do COI, ao que parece, nos ajudam a solucionar propositadamente os princípios OOP / SOLID e encobri-los.

Suamere
fonte
13
+1 A correlação entre IOCe Explicitness of codeé exatamente com isso que tenho problemas. A DI manual pode facilmente se tornar uma tarefa árdua, mas pelo menos coloca o fluxo explícito do programa em um só lugar - "Você consegue o que vê, vê o que recebe". Embora as ligações e declarações dos contêineres do IOC possam se tornar facilmente um programa paralelo oculto por conta própria.
Eugene Podskal 18/09/14
6
Como isso é mesmo uma pergunta?
Stephen
8
Parece mais um post do que uma pergunta.
Bart van Ingen Schenau
4
Eu tenho uma categoria interna para perguntas, "muito argumentativa", que geralmente não é uma razão formal formal, mas que eu voto negativo e às vezes até voto para fechar "não é uma questão real". Porém, ele tem dois aspectos, e essa pergunta atende apenas ao primeiro. (1) A pergunta apresenta uma tese e pergunta se está correta; (2) o questionador responde a respostas que discordam, defendendo a posição inicial. A resposta de Matthew contradiz parte do que a pergunta afirma. Portanto, a menos que o interlocutor rasteje por toda parte, eu pessoalmente o categorizo ​​como uma pergunta de boa fé, embora com a forma "Estou certo, não estou?"
21714 Steve Steveop
3
@BenAaronson Obrigado Ben. Estou tentando comunicar o que aprendi em minha pesquisa e obter respostas sobre a natureza dos contêineres do COI. Como resultado de minha pesquisa, descobri que esses princípios foram quebrados. A melhor resposta para minha pergunta confirmaria ou negaria que os contêineres do COI possam, de alguma forma, fazer coisas que não podemos fazer manualmente sem violar os princípios OOP / SOLID. Por causa de seus ótimos comentários, refatorei minha pergunta com mais exemplos.
Suamere 19/09/14

Respostas:

35

Analisarei seus pontos numericamente, mas primeiro, há algo que você deve ter muito cuidado: não confunda como um consumidor usa uma biblioteca com a maneira como ela é implementada . Bons exemplos disso são o Entity Framework (que você mesmo cita como uma boa biblioteca) e o MVC do ASP.NET. Ambos fazem muita coisa escondida com, por exemplo, reflexão, o que absolutamente não seria considerado um bom design se você o espalhar pelo código do dia-a-dia.

Essas bibliotecas não são absolutamente "transparentes" na maneira como funcionam ou no que estão fazendo nos bastidores. Mas isso não é um prejuízo, porque eles apóiam bons princípios de programação em seus consumidores . Portanto, sempre que você falar sobre uma biblioteca como essa, lembre-se de que, como consumidor de uma biblioteca, não é seu trabalho se preocupar com sua implementação ou manutenção. Você deve se preocupar apenas sobre como isso ajuda ou dificulta o código que você escreve e que usa a biblioteca. Não confunda esses conceitos!

Então, para passar por ponto:

  1. Imediatamente chegamos ao que só posso assumir é um exemplo do exposto acima. Você diz que os contêineres IOC configuram a maioria das dependências como estáticas. Bem, talvez alguns detalhes de implementação de como eles funcionem incluam armazenamento estático (embora, considerando que eles tendem a ter uma instância de um objeto como o Ninject IKernelcomo seu armazenamento principal, duvido disso). Mas essa não é sua preocupação!

    Na verdade, um contêiner de IoC é tão explícito no escopo quanto a injeção de dependência do pobre homem. (Vou continuar comparando os contêineres de IoC com o DI do pobre, porque compará-los com nenhum DI seria injusto e confuso. E, para ficar claro, não estou usando o "DI do pobre homem" como pejorativo.)

    No DI do pobre homem, você instancia uma dependência manualmente e a injeta na classe que precisa. No ponto em que você constrói a dependência, você escolhe o que fazer com ela - armazena-a em uma variável com escopo local, uma variável de classe, uma variável estática, não a armazena de maneira alguma. Você pode passar a mesma instância para várias classes ou criar uma nova para cada classe. Tanto faz. O ponto é que, para ver o que está acontecendo, você olha para o ponto - provavelmente próximo à raiz do aplicativo - onde a dependência é criada.

    Agora, que tal um contêiner de IoC? Bem, você faz exatamente o mesmo! Mais uma vez, indo pela terminologia Ninject, você olhar para onde a ligação está configurado, e descobrir se ele diz algo como InTransientScope, InSingletonScopeou o que quer. Se algo é potencialmente mais claro, porque você tem um código declarando seu escopo, em vez de precisar procurar um método para rastrear o que acontece com algum objeto (um objeto pode ter um escopo definido para um bloco, mas ele é usado várias vezes nesse bloco ou apenas uma vez, por exemplo). Portanto, talvez você se sinta repelido pela idéia de ter que usar um recurso no contêiner de IoC em vez de um recurso de idioma bruto para determinar o escopo, mas contanto que você confie na sua biblioteca de IoC - como deveria! - não há nenhuma desvantagem real .

  2. Ainda não sei o que você está falando aqui. Os contêineres de IoC veem as propriedades privadas como parte de sua implementação interna? Não sei por que eles iriam, mas, novamente, se o fizerem, não é sua preocupação como a biblioteca que você está usando é implementada.

    Ou, talvez, eles exponham uma capacidade como injetar em incubadoras privadas? Honestamente, nunca encontrei isso e duvido que isso seja realmente um recurso comum. Mas, mesmo que esteja lá, é um caso simples de uma ferramenta que pode ser mal utilizada. Lembre-se, mesmo sem um contêiner de IoC, são apenas algumas linhas do código Reflection para acessar e modificar uma propriedade privada. É algo que você quase nunca deve fazer, mas isso não significa que o .NET seja ruim por expor a capacidade. Se alguém de maneira tão óbvia e descontroladamente usa uma ferramenta, a culpa é da pessoa, não da ferramenta.

  3. O ponto final aqui é semelhante a 2. Na grande maioria das vezes, os contêineres de IoC usam o construtor! A injeção de incubadora é oferecida para circunstâncias muito específicas em que, por razões particulares, a injeção de construtor não pode ser usada. Qualquer um que use a injeção de setter o tempo todo para encobrir quantas dependências estão sendo transmitidas está absentamente massivo com a ferramenta. Isso não é culpa da ferramenta, é deles.

    Agora, se esse foi um erro realmente fácil de cometer inocentemente, e um que os contêineres de IoC incentivam, tudo bem, talvez você tenha razão. Seria como tornar todos os membros da minha classe públicos e culpar outras pessoas quando elas modificam coisas que não deveriam, certo? Mas qualquer pessoa que use a injeção de setter para encobrir violações do SRP está deliberadamente ignorando ou completamente ignorando os princípios básicos do projeto. Não é razoável colocar a culpa por isso no contêiner de IoC.

    Isso é especialmente verdade porque também é algo que você pode fazer com a mesma facilidade com o DI do pobre homem:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Realmente, essa preocupação parece completamente ortogonal à utilização ou não de um contêiner de IoC.

  4. Mudar a forma como suas aulas trabalham juntas não é uma violação do OCP. Se fosse, toda inversão de dependência encorajaria uma violação do OCP. Se fosse esse o caso, ambos não estariam no mesmo acrônimo SOLID!

    Além disso, nem os itens a) nem b) chegam perto de ter algo a ver com o OCP. Eu nem sei como responder isso em relação ao OCP.

    A única coisa que posso adivinhar é que você acha que o OCP tem algo a ver com o comportamento não ser alterado no tempo de execução, ou sobre de onde no código o ciclo de vida das dependências é controlado. Não é. OCP não precisa modificar o código existente quando os requisitos são adicionados ou alterados. Trata-se de escrever código, não de como o código que você já escreveu é colado (embora, é claro, o acoplamento flexível seja uma parte importante do alcance do OCP).

E uma última coisa que você diz:

Mas não posso confiar nessa mesma ferramenta de terceiros que está alterando meu código compilado para executar soluções alternativas para coisas que eu não seria capaz de fazer manualmente.

Sim você pode . Não há absolutamente nenhuma razão para você pensar que essas ferramentas - contadas com um grande número de projetos - são mais problemáticas ou propensas a comportamentos inesperados do que qualquer outra biblioteca de terceiros.

Termo aditivo

Acabei de notar que os parágrafos de introdução também podem usar alguns endereços. Você diz ironicamente que os contêineres de IoC "não estão empregando alguma técnica secreta da qual nunca ouvimos falar" para evitar códigos confusos e propensos a duplicação para criar um gráfico de dependência. E você está certo, o que eles estão fazendo é realmente abordar essas coisas com as mesmas técnicas básicas que nós, programadores, sempre fazemos.

Deixe-me falar sobre um cenário hipotético. Você, como programador, monta um aplicativo grande e, no ponto de entrada, onde está construindo seu gráfico de objetos, percebe que possui um código bastante confuso. Existem algumas classes que são usadas repetidamente, e toda vez que você cria uma daquelas, precisa criar toda a cadeia de dependências sob elas novamente. Além disso, você descobre que não possui nenhuma maneira expressiva de declarar ou controlar o ciclo de vida das dependências, exceto com o código personalizado para cada uma. Seu código não está estruturado e está repleto de repetições. Essa é a confusão que você fala no parágrafo de introdução.

Então, primeiro, você começa a refatorar um pouco - onde algum código repetido é estruturado o suficiente para extraí-lo para métodos auxiliares, e assim por diante. Mas então você começa a pensar: esse é um problema que eu poderia resolver em um sentido geral , que não é específico para esse projeto em particular, mas poderia ajudá-lo em todos os seus futuros projetos?

Então, sente-se, pense sobre isso e decida que deve haver uma classe que possa resolver dependências. E você esboça quais métodos públicos seriam necessários:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Binddiz "onde você vê um argumento construtor do tipo interfaceType, passe em uma instância de concreteType". O singletonparâmetro adicional diz se deve usar a mesma instância de concreteTypecada vez ou sempre criar uma nova.

Resolvesimplesmente tentará construir Tcom qualquer construtor que encontrar cujos argumentos são todos os tipos que foram vinculados anteriormente. Ele também pode se chamar recursivamente para resolver as dependências até o fim. Se não conseguir resolver uma instância, porque nem tudo foi vinculado, gera uma exceção.

Você pode tentar implementar isso sozinho e verá que precisa de um pouco de reflexão e algum armazenamento em cache para as ligações, onde singletoné verdade, mas certamente nada drástico ou horrível. E assim que terminar , voilá , você terá o núcleo do seu próprio contêiner de IoC! É realmente tão assustador? A única diferença real entre isso e Ninject ou StructureMap ou Castle Windsor ou o que você preferir é que eles têm muito mais funcionalidade para cobrir os (muitos!) Casos de uso em que essa versão básica não seria suficiente. Mas, no fundo, o que você tem é a essência de um contêiner de IoC.

Ben Aaronson
fonte
Obrigado Ben. Sinceramente, trabalhei com o IOC Containers por cerca de um ano e, embora não o faça, os tutoriais e colegas ao meu redor realmente se concentram na injeção de propriedades (incluindo privadas). É uma loucura, e traz à tona todos os argumentos que faço. Mas mesmo que outras pessoas estejam nessa situação, acho que a melhor coisa que eles podem fazer é aprender mais sobre como os contêineres do COI devem trabalhar para seguir os princípios e apontar esses quebra-princípios nas revisões de código. No entanto ... (continuação)
Suamere
Minha maior preocupação não está necessariamente listada nos Princípios OOP / SOLID, e isso é explicitamente um escopo. Ainda estamos lançando o entendimento de escopo em nível de bloco, local, módulo e global pela janela de uma palavra-chave. Toda a ideia de usar blocos e declarações para determinar quando algo sai do escopo ou é descartado é enorme para mim, e acho que essa foi a parte mais assustadora dos contêineres do COI para mim, para substituir essa parte muito básica do desenvolvimento por uma palavra-chave de extensão .
Suamere 20/09/14
Por isso, marquei sua resposta como a resposta, porque realmente aborda o cerne disso. E da maneira que eu li, é que você só precisa confiar no que os contêineres do COI fazem escondido, porque todo mundo faz. E, quanto às aberturas para possíveis usos indevidos e à maneira elegante que as encobre, é uma preocupação para as revisões de lojas e códigos e eles devem fazer sua diligência para seguir bons princípios de desenvolvimento.
Suamere 20/09/14
2
Em relação a 4, a injeção de dependência não está no acrônimo SOLID . 'D' = 'Princípio da inversão de dependência', que é um conceito totalmente não relacionado.
Astreltsov # 8/15
@astr Bom ponto. Substituí "injeção" por "inversão", pois acho que era isso que eu pretendia escrever em primeiro lugar. Isso ainda é uma simplificação excessiva, pois a inversão de dependência não implica necessariamente várias implementações concretas, mas acho que o significado é claro o suficiente. Seria perigoso depender de uma abstração polimórfica se a troca de implementações fosse uma violação do OCP.
Ben Aaronson
27

Os contêineres do COI não quebram muitos desses princípios?

Em teoria? Não. Os contêineres IoC são apenas uma ferramenta. Você pode ter objetos que têm uma única responsabilidade, interfaces bem separadas, não podem ter seu comportamento modificado depois de gravados etc.

Na prática? Absolutamente.

Quero dizer, não ouvi alguns programadores dizerem que usam contêineres IoC especificamente para permitir que você use alguma configuração para alterar o comportamento do sistema - violando o Princípio Aberto / Fechado. Costumava haver piadas sobre codificação em XML porque 90% de seu trabalho era corrigir a configuração do Spring. E você certamente está certo de que ocultar as dependências (que, reconhecidamente, nem todos os contêineres IoC fazem) é uma maneira rápida e fácil de criar códigos altamente acoplados e muito frágeis.

Vamos ver de uma maneira diferente. Vamos considerar uma classe muito básica que possui uma dependência única e básica. Depende de Actionum delegado ou functor que não aceita parâmetros e retorna void. Qual é a responsabilidade dessa classe? Você não pode dizer. Eu poderia nomear essa dependência como algo claro, o CleanUpActionque faz você pensar que ela faz o que quer que ela faça. Assim que a IoC entra em jogo, ela se torna qualquer coisa . Qualquer um pode entrar na configuração e modificar seu software para fazer coisas praticamente arbitrárias.

"Como isso é diferente de passar um Actionpara o construtor?" você pergunta. É diferente porque você não pode alterar o que está sendo passado para o construtor depois que o software é implantado (ou em tempo de execução!). É diferente porque seus testes de unidade (ou testes de unidade de seus consumidores) cobriram os cenários em que o delegado é passado para o construtor.

"Mas esse é um exemplo falso! Minhas coisas não permitem mudanças de comportamento!" você exclama. Desculpe dizer, mas é verdade. A menos que a interface que você está injetando seja apenas dados. E sua interface não é apenas dados, porque, se fossem apenas dados, você criaria um objeto simples e o utilizaria. Assim que você tem um método virtual no seu ponto de injeção, o contrato da sua classe usando a IoC é "Eu posso fazer qualquer coisa ".

"Como isso difere do uso de scripts para impulsionar as coisas? Isso é perfeitamente adequado". alguns de vocês perguntam. Não é diferente. Do ponto de vista do design do programa, não há diferença. Você está saindo do seu programa para executar código arbitrário. E claro, isso tem sido feito em jogos há muito tempo. É feito em toneladas de ferramentas de administração de sistemas. É POO? Na verdade não.

Não há desculpa para sua onipresença atual. Os contêineres de IoC têm um objetivo sólido - colar suas dependências quando você tem tantas combinações delas, ou elas mudam tão rapidamente que é impraticável explicitar essas dependências. Pouquíssimas empresas realmente se encaixam nesse cenário.

Telastyn
fonte
3
Direito. E muitas lojas afirmam que seu produto é tão complexo que se enquadram nessa categoria, quando na verdade não o fazem. Como é a verdade em muitos cenários.
Suamere 18/09/14
12
Mas a IoC é exatamente o oposto do que você disse. Emprega a abertura / proximidade do programa. Se você pode alterar o comportamento de todo o programa sem precisar alterar o próprio código.
eufórico
6
@Euphoric - aberto para extensão, fechado para modificação. Se você pode alterar o comportamento de todo o programa, ele não está fechado para modificação, não é? A configuração de IoC ainda é seu software, ainda é um código usado por suas classes para fazer o trabalho - mesmo que seja em XML em vez de Java ou C # ou qualquer outra coisa.
Telastyn 19/09/2014
4
@Telastyn O "fechado para modificação" significa que não deve ser necessário alterar (modificar) o código, alterar o comportamento de um todo. Com qualquer configuração externa, você pode apenas apontar para uma nova dll e ter o comportamento de uma alteração inteira.
eufórico
12
@Telastyn Isso não é o que diz o princípio aberto / fechado. Caso contrário, um método que aceita parâmetros violaria o OCP porque eu poderia "modificar" seu comportamento passando parâmetros diferentes. Da mesma forma, na sua versão do OCP, haveria conflito direto com o DI, o que tornaria bastante estranho que ambos apareçam na sigla SOLID.
Ben Aaronson
14

O uso de um contêiner de IoC viola necessariamente os princípios OOP / SOLID? Não .

Você pode usar contêineres IoC de maneira que eles violem os princípios de OOP / SOLID? Sim .

Os contêineres de IoC são apenas estruturas para gerenciar dependências em seu aplicativo.

Você declarou injeções preguiçosas, criadores de propriedades e alguns outros recursos que ainda não senti a necessidade de usar, com os quais concordo que podem fornecer um design abaixo do ideal. No entanto, o uso desses recursos específicos é devido a limitações na tecnologia em que você está implementando os contêineres IoC.

Por exemplo, ASP.NET WebForms, você deve usar a injeção de propriedade, pois não é possível instanciar a Page. Isso é compreensível porque o WebForms nunca foi projetado com paradigmas estritos de OOP em mente.

Por outro lado, o ASP.NET MVC é totalmente possível usar um contêiner de IoC usando a injeção de construtor amigável de OOP / SOLID.

Nesse cenário (usando injeção de construtor), acredito que promova os princípios do SOLID pelos seguintes motivos:

  • Princípio de responsabilidade única - Ter muitas classes menores permite que essas classes sejam muito específicas e tenham uma razão para existir. O uso de um contêiner de IoC facilita a organização deles.

  • Aberto / Fechado - É aqui que o uso de um contêiner de IoC realmente brilha. Você pode estender a funcionalidade de uma classe injetando dependências diferentes. Ter dependências injetadas permite modificar o comportamento dessa classe. Um bom exemplo é mudar a classe que você está injetando, escrevendo um decorador para dizer adicionar cache ou log. Você pode alterar completamente as implementações de suas dependências, se escritas em um nível apropriado de abstração.

  • Princípio da substituição de Liskov - Não é realmente relevante

  • Princípio de Segregação de Interface - Você pode atribuir várias interfaces a um único momento concreto, se desejar, qualquer contêiner de IoC que valha um grão de sal fará isso facilmente.

  • Inversão de Dependência - Os contêineres IoC permitem fazer inversão de dependência facilmente.

Você define um monte de dependências e declara os relacionamentos e escopos, e bing bang boom: você magicamente tem algum complemento que altera seu código ou IL em algum momento e faz as coisas funcionarem. Isso não é explícito ou transparente.

É explícito e transparente; você precisa informar ao seu contêiner de IoC como conectar dependências e como gerenciar a vida útil do objeto. Isso pode ser por convenção, por arquivos de configuração ou por código escrito nos principais aplicativos do seu sistema.

Concordo que, quando escrevo ou leio código usando contêineres IOC, ele se livra de algum código um pouco deselegante.

Essa é toda a razão do OOP / SOLID, para tornar os sistemas gerenciáveis. O uso de um contêiner de IoC está alinhado com esse objetivo.

O escritor dessa classe diz: "Se não usássemos um contêiner IOC, o Construtor teria uma dúzia de parâmetros !!! Isso é horrível!"

Uma classe com 12 dependências provavelmente é uma violação do SRP, não importa em que contexto você a esteja usando.

Mateus
fonte
2
Excelente resposta. Além disso, acho que todos os principais contêineres do COI também suportam AOP, o que pode ajudar muito com o OCP. Para questões transversais, a AOP é geralmente uma rota mais favorável ao OCP do que um Decorador.
Ben Aaronson
2
@BenAaronson Considerando que o AOP injeta código em uma classe, eu não o chamaria de mais amigável ao OCP. Você está abrindo e alterando com força uma classe no compile / runtime.
Doval
11
@Doval Não vejo como está injetando código em uma classe. Note que estou falando do estilo Castle DynamicProxy, onde você tem um interceptador, em vez do estilo de tecelagem de IL. Mas um interceptador é essencialmente apenas um decorador que usa alguma reflexão para não precisar se acoplar a uma interface específica.
Ben Aaronson
"Inversão de dependência - os contêineres de IoC permitem fazer a inversão de dependência facilmente" - eles não fazem, estão simplesmente aproveitando algo que é benéfico, independentemente de você ter IoC ou não. O mesmo acontece com outros princípios.
Den
Eu realmente não entendo a lógica da explicação de que algo não é ruim com base no fato, que é uma estrutura. Os ServiceLocators também são criados como estruturas e são considerados ruins :).
Ipavlu 10/09/17
5

Os contêineres do COI não quebram e permitem que os codificadores quebrem muitos princípios OOP / SOLID?

A staticpalavra-chave em C # permite que os desenvolvedores violem muitos princípios OOP / SOLID, mas ainda é uma ferramenta válida nas circunstâncias certas.

Os contêineres de IoC permitem código bem estruturado e reduzem a carga cognitiva para os desenvolvedores. Um contêiner de IoC pode ser usado para facilitar os seguintes princípios do SOLID. Pode ser mal utilizado, claro, mas se excluirmos o uso de qualquer ferramenta que possa ser mal utilizada, teremos que desistir completamente da programação.

Portanto, embora esse discurso exponha alguns pontos válidos sobre código mal escrito, não é exatamente uma pergunta e não é exatamente útil.

Stephen
fonte
É meu entendimento dos contêineres do IOC que, para gerenciar o escopo, a preguiça e a acessibilidade, ele altera o código compilado para fazer coisas que quebram o OOP / SOLID, mas oculta essa implementação em uma estrutura legal. Mas sim, além disso, abre outras portas para uso incorreto. A melhor resposta para minha pergunta seria algum esclarecimento sobre como os contêineres do COI são implementados, adicionando ou negando minha própria pesquisa.
Suamere 19/09/14
2
@ Suamere - Eu acho que você pode estar usando uma definição muito ampla de COI. O que eu uso não modifica o código compilado. Existem muitas outras ferramentas que não são do COI que também modificam o código compilado. Talvez seu título seja mais parecido com: "A modificação do código compilado quebra ...".
Cerad 19/09/14
@Suamere Eu não sou tão experiente com IoC, mas nunca ouvi falar de IoC alterando código compilado. Você poderia fornecer informações sobre de quais plataformas / idiomas / estruturas você está falando.
Eufórico
11
"Um contêiner de IoC pode ser usado para facilitar os seguintes princípios do SOLID." - você pode elaborar por favor? Explicar como especificamente ajuda em cada princípio seria útil. E é claro que alavancar algo não é o mesmo que ajudar algo a acontecer (por exemplo, DI).
Den
11
@Euphoric Eu não sei sobre os frameworks .net que suamere estava falando, mas o Spring certamente faz modificações nos códigos. O exemplo mais óbvio é o suporte à injeção baseada em método (em que substitui um método abstrato em sua classe para retornar uma dependência que ele gerencia). Ele também faz uso extensivo de proxies gerados automaticamente, a fim de agrupar seu código para fornecer comportamentos adicionais (por exemplo, para gerenciar transações automaticamente). Muito disso, na verdade, não está relacionado ao seu papel como estrutura de DI.
Jules
3

Vejo vários erros e mal-entendidos na sua pergunta. A primeira delas é a falta de distinção entre injeção de propriedade / campo, injeção de construtor e localizador de serviço. Houve muita discussão (por exemplo, flamewars) sobre qual é o caminho certo para fazer as coisas. Na sua pergunta, você parece assumir apenas injeção de propriedade e ignora a injeção de construtor.

A segunda coisa é que você assume que os contêineres de IoC afetam de alguma forma o design do seu código. Eles não, ou pelo menos, não deve ser necessário alterar o design do seu código se você usar a IoC. Seus problemas com classes com muitos construtores são casos de IoC ocultando problemas reais com seu código (possivelmente uma classe que interrompe o SRP) e dependências ocultas são um dos motivos pelos quais as pessoas desencorajam o uso de injeção de propriedade e localizador de serviço.

Não entendo de onde vem o problema do princípio aberto-fechado, então não vou comentar. Mas está faltando uma parte do SOLID, que tem grande influência nisso: princípio da Inversão da dependência. Se você seguir o DIP, descobrirá que a inversão de uma dependência introduz uma abstração, cuja implementação precisa ser resolvida quando uma instância da classe é criada. Com essa abordagem, você terminará com muitas classes, que exigem que várias interfaces sejam realizadas e fornecidas na construção para funcionar corretamente. Se você fornecer essas dependências manualmente, precisará fazer muito trabalho adicional. Os contêineres de IoC facilitam a execução desse trabalho e até permitem a alteração sem a necessidade de recompilar o programa.

Portanto, a resposta para sua pergunta é não. Os contêineres de IoC não violam os princípios de design do POO. Muito pelo contrário, eles resolvem problemas causados ​​pelo cumprimento dos princípios do SOLID (especialmente o princípio da dependência-inversão).

Eufórico
fonte
Tentei aplicar a IoC (Autofac) recentemente em um projeto não trivial. Percebi que eu tinha que fazer um trabalho semelhante com construções não relacionadas ao idioma (new () vs API): especificar o que deveria ser resolvido para interfaces e o que deveria ser resolvido para classes concretas, especificar o que deveria ser uma instância e o que deveria ser um singleton, garantir que a injeção de propriedades funcione corretamente para aliviar uma dependência circular. Decidiu abandonar. Funciona bem com controladores no ASP MVC.
Den
@ Den O seu projeto não foi obviamente projetado com a inversão de dependência em mente. E em nenhum lugar eu disse que usar a IoC é trivial.
eufórico
Na verdade, essa é a coisa - estou usando a Inversão de Dependências desde o início. A IoC que afeta o design além disso é a minha maior preocupação. Por exemplo, por que eu usaria interfaces em todos os lugares apenas para simplificar a IoC? Veja também o principal comentário sobre a questão.
Den
@ Den você tecnicamente não "precisa" usar interfaces. As interfaces ajudam na zombaria para testes de unidade. Você também pode marcar as coisas que você precisa para zombar do virtual.
19417 Fred