Atualmente, estou tentando descobrir o SOLID. Portanto, o Princípio de Inversão de Dependência significa que quaisquer duas classes devem se comunicar por meio de interfaces, não diretamente. Exemplo: Se class A
tiver um método, que espera um ponteiro para um objeto do tipo class B
, esse método deve realmente esperar um objeto do tipo abstract base class of B
. Isso também ajuda no processo Abrir / Fechar.
Desde que entendi corretamente, minha pergunta seria: é uma boa prática aplicar isso a todas as interações de classe ou devo tentar pensar em termos de camadas ?
A razão de eu ser cético é porque estamos pagando algum preço por seguir esse princípio. Diga, eu preciso implementar o recurso Z
. Após análise, concluo que a funcionalidade Z
consiste de funcionalidade A
, B
e C
. I criar uma fachada de classe Z
, que, por meio de interfaces, usa classes A
, B
e C
. I começar a codificar a implementação e em algum momento eu percebo essa tarefa Z
na verdade consiste de funcionalidade A
, B
e D
. Agora preciso descartar a C
interface, o C
protótipo de classe e escrever uma D
interface e uma classe separadas . Sem interfaces, apenas a classe precisaria ser substituída.
Em outras palavras, para mudar alguma coisa, preciso alterar 1. o chamador 2. a interface 3. a declaração 4. a implementação. Em uma implementação diretamente acoplada em python, eu precisaria alterar apenas a implementação.
fonte
Respostas:
Em muitos cartuns ou outras mídias, as forças do bem e do mal são frequentemente ilustradas por um anjo e um demônio sentados nos ombros do personagem. Em nossa história aqui, em vez do bem e do mal, temos o SOLID em um ombro e YAGNI (você não vai precisar!) Sentado no outro.
Os princípios do SOLID levados ao máximo são mais adequados para sistemas corporativos enormes, complexos e ultra-configuráveis. Para sistemas menores ou mais específicos, não é apropriado tornar tudo ridiculamente flexível, pois o tempo que você gasta abstraindo coisas não será um benefício.
Às vezes, passar interfaces em vez de classes concretas significa, por exemplo, que você pode facilmente trocar a leitura de um arquivo para um fluxo de rede. No entanto, para uma grande quantidade de projetos de software, esse tipo de flexibilidade nem sempre será necessário, e você também pode apenas passar em classes de arquivos concretas e chamá-lo por dia e poupar suas células cerebrais.
Parte da arte do desenvolvimento de software é ter uma boa noção do que é provável que mude com o passar do tempo e do que não é. Para as coisas que provavelmente mudarão, use as interfaces e outros conceitos do SOLID. Para as coisas que não funcionam, use o YAGNI e apenas passe tipos concretos, esqueça as classes de fábrica, esqueça todo o tempo de execução da instalação e configuração, etc. e esqueça muitas abstrações do SOLID. Na minha experiência, a abordagem YAGNI provou estar correta com muito mais frequência do que não.
fonte
File
, portanto, será necessário umIFile
trabalho concluído". Então eles não podem substituir facilmente um fluxo de rede, porque exigiram demais a interface e há operações noIFile
método que nem sequer usam, que não se aplicam aos soquetes, portanto, um soquete não pode ser implementadoIFile
. Uma das coisas DI não é uma bala de prata para, está inventando as abstrações direito (interfaces) :-)Nas palavras dos leigos:
A aplicação do DIP é fácil e divertida . Não conseguir o design correto na primeira tentativa não é motivo suficiente para desistir completamente do DIP.
Por outro lado, a programação com interfaces e OOD pode trazer de volta a alegria ao ofício às vezes obsoleto da programação.
Algumas pessoas dizem que isso acrescenta complexidade, mas acho que o opossito é verdadeiro. Mesmo para pequenos projetos. Isso facilita o teste / zombaria. Faz com que seu código tenha menos
case
instruções ou aninhadasifs
. Reduz a complexidade ciclomática e faz você pensar de maneiras novas. Torna a programação mais semelhante ao design e fabricação do mundo real.fonte
Use inversão de dependência onde isso fizer sentido.
Um contra-exemplo extremo é a classe "string" incluída em muitos idiomas. Representa um conceito primitivo, essencialmente uma matriz de caracteres. Supondo que você possa alterar essa classe principal, não faz sentido usar o DI aqui, porque você nunca precisará trocar o estado interno por outra coisa.
Se você possui um grupo de objetos usados internamente em um módulo que não são expostos a outros módulos ou reutilizados em qualquer lugar, provavelmente não vale a pena o esforço para usar o DI.
Há dois lugares em que o DI deve ser usado automaticamente na minha opinião:
Em módulos projetados para extensão. Se todo o objetivo de um módulo é estendê-lo e alterar o comportamento, faz todo o sentido incluir o DI desde o início.
Nos módulos que você está refatorando para fins de reutilização de código. Talvez você tenha codificado uma classe para fazer alguma coisa, depois perceba que, com um refator, você pode aproveitar esse código em outro lugar e é necessário fazê-lo . Esse é um ótimo candidato para DI e outras alterações de extensibilidade.
As chaves aqui são usadas onde for necessário, pois introduzirão complexidade extra e certifique-se de medir as necessidades através de requisitos técnicos (ponto um) ou revisão quantitativa de código (ponto dois).
A DI é uma ótima ferramenta, mas, como qualquer ferramenta *, pode ser usada em excesso ou mal.
* Exceção à regra acima: uma serra alternativa é a ferramenta perfeita para qualquer trabalho. Se não corrigir o problema, ele será removido. Permanentemente.
fonte
String
, há muitos casos em que representações alternativas seriam úteis se o tipo tivesse um bom conjunto de operações virtuais (por exemplo, copie uma substring para uma parte especificada de ashort[]
, relate se substring contém ou pode conter apenas ASCII, tente copiar uma substring que se acredita conter apenas ASCII para uma parte especificada de umbyte[]
, etc.) É uma pena que estruturas não tenham seus tipos de cadeias implementando interfaces úteis relacionadas a cadeias.Parece-me que falta à pergunta original parte do ponto do DIP.
Para realmente tirar proveito do DIP, você deve criar a classe Z primeiro e chamar a funcionalidade das classes A, B e C (que ainda não foram desenvolvidas). Isso fornece a API para as classes A, B e C. Em seguida, você cria as classes A, B e C e preenche os detalhes. Você efetivamente deve criar as abstrações necessárias ao criar a classe Z, com base inteiramente no que a classe Z precisa. Você pode até escrever testes em torno da classe Z antes que as classes A, B ou C sejam escritas.
Lembre-se de que o DIP diz que "Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações".
Depois de determinar o que a classe Z precisa e a maneira como deseja obter o que precisa, você poderá preencher os detalhes. Claro, às vezes é necessário fazer alterações na classe Z, mas 99% das vezes não será esse o caso.
Nunca haverá uma classe D porque você descobriu que Z precisa de A, B e C antes de serem escritas. Uma mudança nos requisitos é uma história completamente diferente.
fonte
A resposta curta é "quase nunca", mas existem, de fato, alguns lugares onde o DIP não faz sentido:
Fábricas ou construtores, cujo trabalho é criar objetos. Estes são essencialmente os "nós das folhas" em um sistema que abraça totalmente a IoC. Em algum momento, algo precisa realmente criar seus objetos e não pode depender de mais nada para fazer isso. Em muitos idiomas, um contêiner de IoC pode fazer isso por você, mas às vezes você precisa fazer da maneira antiga.
Implementações de estruturas de dados e algoritmos. Geralmente, nesses casos, as principais características que você otimiza (como tempo de execução e complexidade assintótica) dependem do uso de tipos de dados específicos. Se você estiver implementando uma tabela de hash, realmente precisará saber que está trabalhando com uma matriz para armazenamento, não uma lista vinculada, e apenas a própria tabela sabe como alocar adequadamente as matrizes. Você também não deseja passar em uma matriz mutável e fazer com que o chamador quebre sua tabela de hash mexendo no conteúdo.
Classes de modelo de domínio . Eles implementam sua lógica de negócios e (na maioria das vezes) faz sentido ter uma implementação, porque (na maioria das vezes) você está apenas desenvolvendo o software para uma empresa. Embora algumas classes de modelo de domínio possam ser construídas usando outras classes de modelo de domínio, isso geralmente ocorrerá caso a caso. Como os objetos de modelo de domínio não incluem nenhuma funcionalidade que possa ser zombada de maneira útil, não há benefícios de testabilidade ou manutenção no DIP.
Quaisquer objetos que são fornecidos como uma API externa e precisam criar outros objetos, cujos detalhes de implementação você não deseja expor publicamente. Isso se enquadra na categoria geral de "o design da biblioteca é diferente do design do aplicativo". Uma biblioteca ou estrutura pode fazer uso liberal de DI internamente, mas eventualmente terá que fazer algum trabalho real, caso contrário, não é uma biblioteca muito útil. Digamos que você esteja desenvolvendo uma biblioteca de rede; você realmente não deseja que o consumidor possa fornecer sua própria implementação de um soquete. Você pode usar internamente uma abstração de um soquete, mas a API que você expõe aos chamadores criará seus próprios soquetes.
Testes unitários e duplos. Falsificações e tocos devem fazer uma coisa e fazê-lo simplesmente. Se você tem um falso que é complexo o suficiente para se preocupar em fazer ou não a injeção de dependência, provavelmente é muito complexo (talvez porque esteja implementando uma interface também muito complexa).
Pode haver mais; estes são os que eu vejo com certa frequência.
fonte
Alguns sinais de que você pode estar aplicando o DIP em um nível muito micro, onde não está fornecendo valor:
Se é isso que você está vendo, é melhor ter Z chamando C diretamente e pular a interface.
Além disso, não penso na decoração de métodos por uma estrutura de injeção de dependência / proxy dinâmico (Spring, Java EE) da mesma maneira que o verdadeiro SOLID DIP - isso é mais como um detalhe de implementação de como a decoração de métodos funciona nessa pilha de tecnologia. A comunidade Java EE considera uma melhoria que você não precisa de pares Foo / FooImpl como costumava fazer ( referência ). Por outro lado, o Python suporta decoração de funções como um recurso de linguagem de primeira classe.
Veja também esta postagem no blog .
fonte
Se você sempre inverter suas dependências, todas elas estarão de cabeça para baixo. O que significa que se você começou com um código confuso com um nó de dependências, é isso que você ainda tem (realmente), apenas invertido. É aí que você obtém o problema de que todas as alterações em uma implementação também precisam mudar sua interface.
O ponto de inversão de dependência é que você inverte seletivamente as dependências que estão deixando as coisas complicadas. Os que devem ir de A a B para C ainda o fazem, são os que estavam passando de C para A que agora passam de A para C.
O resultado deve ser um gráfico de dependência livre de ciclos - um DAG. Existem várias ferramentas que verificarão essa propriedade e desenharão o gráfico.
Para uma explicação mais completa, consulte este artigo :
fonte