Eu suspeito que cometi um erro de estudante aqui e estou procurando esclarecimentos. Muitas das classes na minha solução (C #) - ouso dizer a maioria - acabei escrevendo uma interface correspondente. Por exemplo, uma interface "ICalculator" e uma classe "Calculator" que a implementa, mesmo que eu nunca substitua essa calculadora por uma implementação diferente. Além disso, a maioria dessas classes reside no mesmo projeto que suas dependências - elas realmente precisam apenas estar internal
, mas acabaram sendo public
um efeito colateral da implementação de suas respectivas interfaces.
Eu acho que essa prática de criar interfaces para tudo resultou de algumas falsidades:
1) Originalmente, pensei que uma interface era necessária para criar simulações de teste de unidade (estou usando o Moq), mas desde então descobri que uma classe pode ser ridicularizada se seus membros forem virtual
e tem um construtor sem parâmetros (corrija-me se Estou errado).
2) Originalmente, pensei que uma interface era necessária para registrar uma classe na estrutura de IoC (Castle Windsor), por exemplo
Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...
quando na verdade eu poderia simplesmente registrar o tipo concreto contra ele mesmo:
Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...
3) O uso de interfaces, por exemplo, parâmetros do construtor para injeção de dependência, resulta em "acoplamento frouxo".
Então, eu fiquei louco com interfaces ?! Estou ciente dos cenários em que você "normalmente" usaria uma interface, por exemplo, expondo uma API pública ou para coisas como a funcionalidade "conectável". Minha solução possui um pequeno número de classes que se encaixam nesses casos de uso, mas gostaria de saber se todas as outras interfaces são desnecessárias e devem ser removidas? Em relação ao ponto 3) acima, não estarei violando o "acoplamento solto" se eu fizer isso?
Edit : - Estou apenas brincando com o Moq, e parece exigir que os métodos sejam públicos e virtuais e tenham um construtor público sem parâmetros, para poder zombar deles. Então parece que não posso ter aulas internas?
fonte
Respostas:
Em geral, se você criar uma interface com apenas um implementador, estará escrevendo as coisas duas vezes e perdendo seu tempo. As interfaces por si só não fornecem acoplamento frouxo se estiverem fortemente acopladas a uma implementação ... Dito isto, se você quiser zombar dessas coisas em testes de unidade, isso geralmente é um ótimo sinal de que você inevitavelmente precisará de mais de um implementador em tempo real. código.
Eu consideraria um pouco de cheiro de código se quase todas as suas classes tiverem interfaces. Isso significa que eles estão quase todos trabalhando um com o outro de uma maneira ou de outra. Eu suspeitaria que as classes estão fazendo muito (já que não há classes auxiliares) ou você abstraiu demais (oh, eu quero uma interface de provedor para gravidade, pois isso pode mudar!).
fonte
1) Mesmo que as classes concretas sejam ridículas, ainda é necessário que você crie membros
virtual
e forneça um construtor sem parâmetros, que pode ser intrusivo e indesejado. Você (ou novos membros da equipe) logo se encontrará sistematicamente adicionandovirtual
construtores sem parâmetros e em cada nova classe sem pensar duas vezes, apenas porque "é assim que as coisas funcionam".Uma interface é uma idéia muito melhor para a IMO quando você precisa zombar de uma dependência, porque permite adiar a implementação até quando você realmente precisa dela e cria um bom contrato claro da dependência.
3) Como isso é uma falsidade?
Acredito que as interfaces são ótimas para definir protocolos de mensagens entre objetos de colaboração. Pode haver casos em que a necessidade deles seja discutível, como em entidades de domínio, por exemplo. No entanto, onde quer que você tenha um parceiro estável (por exemplo, uma dependência injetada em oposição a uma referência transitória) com a qual precisa se comunicar, considere o uso de uma interface.
fonte
A idéia da IoC é tornar os tipos de concreto substituíveis. Portanto, mesmo que (no momento) você tenha apenas uma única calculadora que implemente o ICalculator, uma segunda implementação poderá depender apenas da interface e não dos detalhes da calculadora.
Portanto, a primeira versão do seu registro IoC-Container é a correta e a que você deve usar no futuro.
Se você tornar suas aulas públicas ou internas, não está realmente relacionado, e nem o moq-ing.
fonte
Eu realmente ficaria mais preocupado com o fato de que você parece sentir a necessidade de "zombar" de todos os tipos em todo o projeto.
As duplas de teste são úteis principalmente para isolar seu código do mundo externo (bibliotecas de terceiros, E / S de disco, E / S de rede etc.) ou de cálculos realmente caros. Ser muito liberal com sua estrutura de isolamento (você realmente precisa? O seu projeto é tão complexo?) Pode facilmente levar a testes acoplados à implementação e torna seu design mais rígido. Se você escrever vários testes que verificam se alguma classe chamou o método X e Y nessa ordem e depois passou os parâmetros a, bec para o método Z, você realmente está obtendo valor? Às vezes, você faz testes como esse ao testar o log ou a interação com bibliotecas de terceiros, mas deve ser a exceção, não a regra.
Assim que sua estrutura de isolamento diz que você deve tornar as coisas públicas e que sempre deve ter construtores sem parâmetros, é hora de se esquivar, porque neste momento sua estrutura está forçando você a escrever códigos ruins (piores).
Falando mais especificamente sobre interfaces, acho que elas são úteis em alguns casos importantes:
Quando você precisa despachar chamadas de métodos para tipos diferentes, por exemplo, quando você tem várias implementações (essa é a óbvia, já que a definição de uma interface na maioria dos idiomas é apenas uma coleção de métodos virtuais)
Quando você precisar isolar o restante do seu código de alguma classe. Essa é a maneira normal de abstrair o sistema de arquivos e o acesso à rede e o banco de dados e assim por diante da lógica principal do seu aplicativo.
Quando você precisar de inversão de controle para proteger os limites do aplicativo. Essa é uma das maneiras mais poderosas possíveis para gerenciar dependências. Todos sabemos que os módulos de alto nível e os de baixo nível não devem saber um do outro, porque eles mudarão por diferentes motivos e você NÃO deseja ter dependências no HttpContext no modelo de domínio ou em alguma estrutura de renderização de PDF na biblioteca de multiplicação de matrizes . Fazer com que suas classes de fronteira implementem interfaces (mesmo que nunca sejam substituídas) ajuda a afrouxar o acoplamento entre as camadas e reduz drasticamente o risco de dependências penetrarem nas camadas por tipos que conhecem muitos outros tipos.
fonte
Eu me inclino para a ideia de que, se alguma lógica for privada, a implementação dessa lógica não deve ser motivo de preocupação para ninguém e, como tal, não deve ser testada. Se alguma lógica é complexa o suficiente para você querer testá-la, essa lógica provavelmente tem um papel distinto e, portanto, deve ser pública. Por exemplo, se eu extrair algum código em um método auxiliar particular, acho que geralmente é algo que poderia estar em uma classe pública separada (um formatador, um analisador, um mapeador, o que for).
Quanto às interfaces, deixo que a necessidade de stub ou zombar da classe me leve. Coisas como repositórios, geralmente quero ser capaz de stub ou zombar. Um mapeador em um teste de integração, (geralmente) nem tanto.
fonte