Declarar interface no mesmo arquivo que a classe base, é uma boa prática?

29

Para ser intercambiável e testável, normalmente os serviços com lógica precisam ter interface, por exemplo

public class FooService: IFooService 
{ ... }

Em termos de design, concordo com isso, mas uma das coisas que me incomoda com essa abordagem é que, para um serviço, você precisará declarar duas coisas (a classe e a interface) e, em nossa equipe, normalmente dois arquivos (um para a classe e um para a interface). Outro desconforto é a dificuldade na navegação, pois o uso de "Ir para definição" no IDE (VS2010) apontará para a interface (já que outras classes se referem à interface), não à classe real.

Eu estava pensando que escrever o IFooService no mesmo arquivo que o FooService reduzirá a estranheza acima. Afinal, IFooService e FooService são muito relacionados. Esta é uma boa prática? Existe uma boa razão para que o IFooService precise estar localizado em seu próprio arquivo?

Louis Rhys
fonte
3
Se você tiver apenas uma implementação de uma interface específica, não há necessidade lógica real da interface. Por que não usar a classe diretamente?
Joris Timmermans
11
@MadKeithV para acoplamento solto e testabilidade?
Louis Rhys
10
@ MadKeithV: você está correto. Porém, quando você escreve testes de unidade para código que depende do IFooService, normalmente fornece um MockFooService, que é uma segunda implementação dessa interface.
Doc Brown
5
@ DocBrown - se você tem várias implementações, obviamente o comentário desaparece, mas o utilitário também pode ser direcionado diretamente para a "implementação única" (porque há pelo menos duas). A pergunta então é: quais informações estão na implementação concreta que ainda não deveriam estar na descrição / documentação da interface no ponto em que você está usando a interface?
Joris Timmermans
1
Autopublicidade flagrante: stackoverflow.com/questions/5840219/…
Marjan Venema

Respostas:

24

Ele não precisa estar em seu próprio arquivo, mas sua equipe deve decidir sobre um padrão e cumpri-lo.

Além disso, você está certo de que "Ir para a definição" leva você à interface, mas se você tiver o Resharper instalado, basta apenas um clique para abrir uma lista de classes / interfaces derivadas dessa interface, portanto não é grande coisa. É por isso que mantenho a interface em um arquivo separado.

 

Scott Whitlock
fonte
5
Concordo com esta resposta, afinal o IDE deve se ajustar ao nosso design e não o contrário, certo?
marktani
1
Além disso, sem o Resharper, F12 e Shift + F12 praticamente fazem a mesma coisa. Nem mesmo um clique imediato :) (para ser justo, ele só irá levá-lo para usos diretos da interface, não sei o que a versão ReSharper faz)
Daniel B
@DanielB: No Resharper, o Alt-End vai para a implementação (ou apresenta uma lista de possíveis implementações para ir).
StriplingWarrior
@StriplingWarrior, parece que é exatamente o que o vanilla VS faz também. F12 = vá para a definição, Shift + F12 = vá para a implementação (eu tenho certeza que isso funciona sem o Resharper, embora eu o tenha instalado, por isso é difícil confirmar). É um dos atalhos de navegação mais úteis, estou apenas espalhando a palavra.
Daniel B13
@ DanielB: O padrão para Shift-F12 é "encontrar usos". jetbrains.com/resharper/webhelp/...
StriplingWarrior
15

Eu acho que você deve mantê-los arquivos separados. Como você disse, a idéia é permanecer testável e intercambiável. Ao colocar a interface no mesmo arquivo que sua implementação, você está associando a interface à implementação específica. Se você decidir criar um objeto simulado ou outra implementação, a interface não será logicamente separada do FooService.

Brad S
fonte
10

De acordo com o SOLID, você não apenas deve criar a interface, mas também em um arquivo diferente, em um assembly diferente.

Por quê? Como qualquer alteração em um arquivo de origem que é compilado em um assembly requer recompilação do assembly, e qualquer alteração em um assembly requer recompilação de qualquer assembly dependente. Portanto, se seu objetivo, com base no SOLID, é poder substituir uma implementação A por uma implementação B, enquanto a classe C dependente da interface I não precisa saber a diferença, é necessário garantir que a montagem com I nele não muda, protegendo assim os usos.

"Mas é apenas uma recompilação" eu ouço você protestar. Bem, pode ser, mas no aplicativo para smartphone, que é mais fácil com a largura de banda de dados dos usuários; baixar um binário que mudou ou fazer o download desse binário e cinco outros com código que depende dele? Nem todo programa é escrito para ser consumido por computadores de mesa em uma LAN. Mesmo nesse caso, onde a largura de banda e a memória são baratas, os lançamentos menores de patches podem ter valor porque são triviais para serem enviados a toda a LAN através do Active Directory ou de camadas de gerenciamento de domínio semelhantes; seus usuários aguardarão apenas alguns segundos para que sejam aplicados na próxima vez que fizerem login, em vez de alguns minutos para que tudo seja reinstalado. Sem mencionar que, quanto menos montagens precisarem ser recompiladas ao criar um projeto, mais rápido ele será construído,

Agora, o aviso: nem sempre é possível ou possível fazer isso. A maneira mais fácil de fazer isso é criar um projeto "interfaces" centralizado. Isso tem suas próprias desvantagens; o código se torna menos reutilizável porque o projeto de interface E o projeto de implementação precisam ser referenciados em outros aplicativos que reutilizam a camada de persistência ou outros componentes principais do seu aplicativo. Você pode superar esse problema dividindo as interfaces em montagens mais firmemente acopladas, mas você tem mais projetos em seu aplicativo, o que torna muito difícil uma compilação completa. A chave é o equilíbrio e a manutenção do design fracamente acoplado; geralmente você pode mover os arquivos conforme necessário, portanto, quando perceber que uma classe precisará de muitas alterações ou que novas implementações de uma interface serão necessárias regularmente (talvez para fazer a interface com versões de outro software com suporte recente,

KeithS
fonte
8

Por que ter arquivos separados é um desconforto? Para mim, é muito mais limpo e arrumado. É comum criar uma subpasta chamada "Interfaces" e colar seus arquivos IFooServer.cs lá, se você quiser ver menos arquivos no Gerenciador de Soluções.

A razão pela qual a interface é definida em seu próprio arquivo é pelo mesmo motivo que as classes geralmente são definidas em seu próprio arquivo: o gerenciamento de projetos é mais simples quando a estrutura lógica e a estrutura de arquivos são idênticas, para que você sempre saiba qual arquivo uma determinada classe está definida. in. Isso pode facilitar sua vida ao depurar (os rastreamentos de pilha de exceção geralmente fornecem números de arquivo e linha) ou ao mesclar o código-fonte em um repositório de controle de origem.

Avner Shahar-Kashtan
fonte
6

Geralmente, é uma boa prática permitir que seus arquivos de código contenham apenas uma única classe ou uma única interface. Mas essas práticas de codificação são um meio para atingir um fim - para estruturar melhor seu código, facilitando o trabalho. Se você e sua equipe acham mais fácil trabalhar com as classes mantidas juntas com suas interfaces, faça isso de qualquer maneira.

Pessoalmente, prefiro ter a interface e a classe no mesmo arquivo quando houver apenas uma classe que implemente a interface, como no seu caso.

Em relação aos problemas que você tem com a navegação, recomendo o ReSharper . Ele contém alguns atalhos altamente úteis para ir diretamente para o método que implementa um método de interface específico.

Pete
fonte
3
+1 por apontar que as práticas da equipe devem ditar a estrutura do arquivo e por ter o contrário da abordagem da norma.
3

Há muitas vezes em que você deseja que sua interface não esteja apenas em um arquivo separado para a classe, mas mesmo em um assembly separado .

Por exemplo, uma interface de contrato de serviço do WCF pode ser compartilhada pelo cliente e pelo serviço se você tiver controle sobre as duas extremidades da ligação. Ao mover a interface para seu próprio assembly, ele terá menos dependências de assembly próprias. Isso facilita muito o consumo do cliente, diminuindo o acoplamento com sua implementação.

StuartLC
fonte
2

Raramente, se é que alguma vez, faz sentido ter uma única implementação de uma interface 1 . Se você colocar uma interface pública e uma classe pública implementando essa interface no mesmo arquivo, é provável que você não precise de uma interface.

Quando a classe que você co-localiza com a interface é abstrata e você sabe que todas as implementações de sua interface devem herdar essa classe abstrata, faz sentido localizar as duas no mesmo arquivo. Você ainda deve examinar sua decisão de usar uma interface: pergunte-se se uma classe abstrata por si só seria adequada e abandone a interface se a resposta for afirmativa.

Geralmente, porém, você deve seguir a estratégia "uma classe / interface pública corresponde a um arquivo": é fácil de seguir e facilita a navegação na árvore de fontes.


1 Uma exceção notável é quando você precisa ter uma interface para fins de teste, porque sua escolha de estrutura de simulação colocou esse requisito adicional em seu código.

dasblinkenlight
fonte
3
Na verdade, é um padrão bastante normal ter uma interface para uma classe no .NET, pois permite que os testes de unidade substituam as dependências por zombarias, stubs, spys ou outras duplas de teste.
Pete
2
@Pete Eu editei minha resposta para incluir isso como uma observação. Eu não chamaria esse padrão de "normal", porque permite que sua preocupação com a testabilidade "vaze" em sua base de código principal. As estruturas modernas de zombaria o ajudam a superar esse problema em grande parte, mas ter uma interface lá definitivamente simplifica bastante as coisas.
precisa saber é o seguinte
2
@KeithS Uma classe sem uma interface deve estar em seu design somente quando você tiver certeza absoluta de que não faz sentido subclassificá-la, e você faz essa classe sealed. Se você suspeitar que em algum momento no futuro possa adicionar uma segunda subclasse, coloque uma interface imediatamente. PS Um assistente decente qualidade refatoração pode ser seu por um preço modesto de uma única licença ReSharper :)
dasblinkenlight
1
@KeithS E sim, todas as suas classes que não são projetadas especificamente para subclasses devem ser seladas. De fato, as classes de desejo foram seladas por padrão, forçando você a designar classes para herança explicitamente, da mesma maneira que o idioma atualmente o força a designar funções para substituir, marcando-as virtual.
precisa saber é o seguinte
2
@KeithS: O que acontece quando sua implementação se torna duas? O mesmo que quando sua implementação muda; você altera o código para refletir essa alteração. YAGNI se aplica aqui. Você nunca saberá o que vai mudar no futuro, assim como a coisa mais simples possível. (Infelizmente testar messes com esta máxima)
Groky
2

As interfaces pertencem a seus clientes e não a suas implementações, conforme definido em Princípios, práticas e padrões ágeis de Robert C. Martin. Assim, combinar interface e implementação no mesmo local é contrário ao seu princípio.

O código do cliente depende da interface. Isso permite a possibilidade de compilar e implantar o código do cliente e a interface sem a implementação. Você pode ter diferentes implementações funcionando como plug - ins no código do cliente.

ATUALIZAÇÃO: Este não é um princípio somente ágil aqui. A Gangue dos Quatro em Design Patterns, em 94, já está falando de clientes aderindo a interfaces e programando para interfaces. A visão deles é semelhante.

Patkos Csaba
fonte
1
O uso de interfaces vai muito além do domínio que o Agile possui. Agile é uma metodologia de desenvolvimento que utiliza construções de linguagem de maneiras particulares. Uma interface é uma construção de linguagem.
1
Sim, mas a interface ainda pertence ao cliente e não à implementação, independentemente do fato de estarmos falando sobre ágil ou não.
Patkos Csaba
Eu estou com você nisso.
Marjan Venema
0

Eu pessoalmente não gosto do "eu" na frente de uma interface. Como usuário desse tipo, não gostaria de ver que essa é uma interface ou não. O tipo é a coisa interessante. Em termos de dependências, seu FooService é UMA implementação possível do IFooService. A interface deve estar próxima do local onde é usada. Por outro lado, a implementação deve estar em um local onde possa ser facilmente alterada sem afetar o cliente. Então, eu preferiria dois arquivos - normalmente.

ollins
fonte
2
Como usuário, quero saber se um tipo é uma interface, porque, por exemplo, significa que não posso usá new-lo, mas, por outro lado, posso usá-lo de forma covariável ou contra-variante. E Ié uma convenção de nomenclatura estabelecida no .Net, por isso é uma boa razão para segui-la, mesmo que seja consistente com outros tipos.
svick
Começando com Ina interface foi apenas a introdução aos meus argumentos sobre a separação em dois arquivos. O código é melhor legível e torna a inversão das dependências mais clara.
Ollins
4
Goste ou não, usar um 'I' na frente de um nome de interface é um padrão de fato no .NET. Não seguir o padrão em um projeto .NET seria, no meu ponto de vista, uma violação do 'princípio de menor espanto'.
Pete
Meu ponto principal é: separar em dois arquivos. Um para a abstração e outro para a implementação. Se o uso do Ié normal em seu ambiente: Use-o! (Mas eu não gosto :))
ollins 11/09/12
E, no caso do P.SE, o prefixo I na frente de uma interface torna mais evidente que o pôster está falando sobre uma interface, não herdando de outra classe.
0

A codificação para interfaces pode ser ruim, é de fato tratada como um antipadrão para certos contextos e não é uma lei. Normalmente, um bom exemplo de codificação para interfaces antipadrão é quando você possui uma interface que possui apenas uma implementação em seu aplicativo. Outra seria se o arquivo de interface se tornar tão incômodo que você precisará ocultar sua declaração no arquivo da classe implementada.

Shivan Dragon
fonte
ter apenas uma implementação em sua aplicação de uma interface não é necessariamente uma coisa ruim; alguém poderia estar protegendo o aplicativo contra mudanças tecnológicas. Por exemplo, uma interface para uma tecnologia de persistência de dados. Você teria apenas uma implementação para a tecnologia atual de persistência de dados enquanto protegia o aplicativo se isso mudasse. Portanto, haveria apenas um diabrete naquele momento.
TSmith 5/06
0

Colocar uma classe e uma interface no mesmo arquivo não impõe limites sobre como eles podem ser usados. Uma interface ainda pode ser usada para zombarias, etc, da mesma maneira, mesmo que esteja no mesmo arquivo que uma classe que a implementa. A questão pode ser entendida apenas como uma conveniência organizacional; nesse caso, eu diria que faça o que facilitar sua vida!

Certamente, alguém poderia argumentar que ter os dois no mesmo arquivo pode levar os incautos a considerá-los mais programaticamente acoplados do que realmente são, o que é um risco.

Pessoalmente, se eu encontrasse uma base de código que usasse uma convenção sobre a outra, eu não ficaria muito preocupado de qualquer maneira.

Holf
fonte
Não é apenas uma "conveniência organizacional" ... é também uma questão de gerenciamento de riscos, gerenciamento de implantação, rastreabilidade de código, controle de alterações, ruído de controle de origem, gerenciamento de segurança. Existem muitos ângulos para essa pergunta.
TSmith