Não é o ponto principal de uma interface que várias classes aderem a um conjunto de regras e implementações?
Não é o ponto principal de uma interface que várias classes aderem a um conjunto de regras e implementações?
Respostas:
A rigor, não, não, YAGNI se aplica. Dito isto, o tempo que você gastará criando a interface é mínimo, especialmente se você tiver uma ferramenta útil de geração de código fazendo a maior parte do trabalho para você. Se você não tem certeza se precisará ou não da interface, eu diria que é melhor errar no sentido de apoiar a definição de uma interface.
Além disso, o uso de uma interface mesmo para uma única classe fornecerá outra implementação simulada para testes de unidade, que não está em produção. A resposta de Avner Shahar-Kashtan se expande nesse ponto.
Eu responderia que se você precisa de uma interface ou não, não depende de quantas classes a implementarão. As interfaces são uma ferramenta para definir contratos entre vários subsistemas do seu aplicativo; então o que realmente importa é como seu aplicativo é dividido em subsistemas. Deve haver interfaces como o front-end para subsistemas encapsulados, não importa quantas classes os implementem.
Aqui está uma regra prática muito útil:
Foo
refira diretamente à classe BarImpl
, você se compromete fortemente a mudar Foo
toda vez que muda BarImpl
. Você basicamente os trata como uma unidade de código dividida em duas classes.Foo
referir à interface Bar
, está se comprometendo a evitar alterações Foo
quando alterar BarImpl
.Se você definir interfaces nos pontos-chave do seu aplicativo, analise cuidadosamente os métodos que eles devem oferecer suporte e quais não devem e comenta claramente as interfaces para descrever como uma implementação deve se comportar (e como não), sua aplicação será muito mais fácil de entender porque essas interfaces comentadas irá fornecer uma espécie de especificação da aplicação, uma descrição de como ele está destinado a se comportar. Isso facilita muito a leitura do código (em vez de perguntar "o que diabos esse código deve fazer", você pode perguntar "como esse código faz o que deveria fazer").
Além de tudo isso (ou realmente por causa disso), as interfaces promovem compilação separada. Como as interfaces são triviais para compilar e têm menos dependências do que suas implementações, significa que se você escrever uma classe Foo
para usar uma interface Bar
, geralmente poderá recompilar BarImpl
sem precisar recompilar Foo
. Em aplicativos grandes, isso pode economizar muito tempo.
Foo
depende da interface Bar
, BarImpl
pode ser modificada sem precisar recompilar Foo
. 4. Controle de acesso mais refinado do que ofertas públicas / privadas (exponha a mesma classe a dois clientes por meio de interfaces diferentes).
If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
Ao alterar o BarImpl, quais são as alterações que podem ser evitadas no Foo usando interface Bar
? Desde que as assinaturas e a funcionalidade de um método não sejam alteradas no BarImpl, o Foo não exigirá alterações, mesmo sem a interface (o objetivo inteiro da interface). Eu estou falando sobre o cenário em que apenas uma classe ou seja, BarImpl
implementa Bar. Para o cenário de várias classes, entendo o Princípio de Inversão de Dependência e como a interface é útil.
As interfaces são designadas para definir um comportamento, isto é, um conjunto de protótipos de funções / métodos. Os tipos que implementam a interface implementam esse comportamento; portanto, quando você lida com esse tipo, sabe (em parte) qual é o comportamento dele.
Não há necessidade de definir uma interface se você souber que o comportamento definido por ela será usado apenas uma vez. BEIJO (mantenha-o simples, estúpido)
Embora em teoria você não deva ter uma interface apenas para ter uma interface, a resposta de Yannis Rizos sugere outras complicações:
Quando você está escrevendo testes de unidade e usando estruturas simuladas, como Moq ou FakeItEasy (para nomear as duas mais recentes que usei), está criando implicitamente outra classe que implementa a interface. A pesquisa do código ou da análise estática pode afirmar que há apenas uma implementação, mas, de fato, há a implementação simulada interna. Sempre que você começar a escrever zombarias, verá que a extração de interfaces faz sentido.
Mas espere, tem mais. Existem mais cenários em que há implementações implícitas de interface. O uso da pilha de comunicação WCF do .NET, por exemplo, gera um proxy para um serviço remoto, que novamente implementa a interface.
Em um ambiente de código limpo, concordo com o restante das respostas aqui. No entanto, preste atenção em todas as estruturas, padrões ou dependências existentes que possam fazer uso de interfaces.
Não, você não precisa deles, e considero um antipadrão criar interfaces automaticamente para todas as referências de classe.
Existe um custo real para fazer Foo / FooImpl para tudo. O IDE pode criar a interface / implementação de graça, mas quando você está navegando no código, você tem uma carga cognitiva extra da F3 / F12 para foo.doSomething()
levá-lo à assinatura da interface e não à implementação real desejada. Além disso, você tem a confusão de dois arquivos em vez de um para tudo.
Portanto, você deve fazê-lo apenas quando realmente precisar de algo.
Agora, abordando os contra-argumentos:
Preciso de interfaces para estruturas de injeção de dependência
As interfaces para suportar estruturas são herdadas. Em Java, as interfaces costumavam ser um requisito para proxies dinâmicos, pré-CGLIB. Hoje, você geralmente não precisa disso. É considerado progresso e um benefício para a produtividade do desenvolvedor que você não precisa mais deles no EJB3, Spring etc.
Preciso de zombaria para testes de unidade
Se você escreve suas próprias zombarias e tem duas implementações reais, uma interface é apropriada. Provavelmente não teríamos essa discussão em primeiro lugar se a sua base de código tivesse um FooImpl e um TestFoo.
Mas se você estiver usando uma estrutura de zombaria como Moq, EasyMock ou Mockito, poderá zombar de classes e não precisará de interfaces. É análogo à configuração foo.method = mockImplementation
em um idioma dinâmico onde os métodos podem ser atribuídos.
Precisamos de interfaces para seguir o DIP (Dependency Inversion Principle)
O DIP diz que você constrói para depender de contratos (interfaces) e não de implementações. Mas uma classe já é um contrato e uma abstração. É para isso que servem as palavras-chave públicas / privadas. Na universidade, o exemplo canônico era algo como uma classe Matrix ou Polynomial - os consumidores têm uma API pública para criar matrizes, adicioná-las etc., mas não têm permissão para se importar se a matriz for implementada de forma esparsa ou densa. Não havia IMatrix ou MatrixImpl necessário para provar esse ponto.
Além disso, o DIP geralmente é super aplicado em todos os níveis de chamada de classe / método, não apenas nos principais limites do módulo. Um sinal de que você está aplicando demais o DIP é que sua interface e implementação mudam na etapa de bloqueio, de modo que você precisa tocar em dois arquivos para fazer uma alteração em vez de em um. Se o DIP for aplicado adequadamente, isso significa que sua interface não precisa ser alterada com frequência. Além disso, outro sinal é que sua interface possui apenas um consumidor real (seu próprio aplicativo). História diferente se você estiver criando uma biblioteca de classes para consumo em muitos aplicativos diferentes.
Este é um corolário do argumento do tio Bob Martin sobre zombaria - você só precisa zombar das principais fronteiras da arquitetura. Em um aplicativo da web, o acesso HTTP e DB são os principais limites. Todas as chamadas de classe / método entre elas não são. O mesmo vale para o DIP.
Veja também:
new Mockup<YourClass>() {}
e toda a classe, incluindo seus construtores, é zombada, seja uma interface, classe abstrata ou classe concreta. Você também pode "substituir" o comportamento do construtor, se desejar. Suponho que existem maneiras equivalentes no Mockito ou Powermock.
Parece que as respostas dos dois lados da cerca podem ser resumidas no seguinte:
Projete bem e coloque interfaces onde as interfaces são necessárias.
Como observei em minha resposta à resposta de Yanni , acho que você nunca poderá ter uma regra rígida e rápida sobre interfaces. A regra precisa ser, por definição, flexível. Minha regra sobre interfaces é que uma interface deve ser usada em qualquer lugar em que você esteja criando uma API. E uma API deve ser criada em qualquer lugar em que você esteja cruzando a fronteira de um domínio de responsabilidade para outro.
Para um exemplo (terrivelmente artificial), digamos que você esteja criando uma Car
classe. Na sua turma, você certamente precisará de uma camada de interface do usuário. Neste exemplo em particular, que toma a forma de um IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
, e BrakePedal
. Como este carro contém um AutomaticTransmission
, você não precisa de um ClutchPedal
. (E como este é um carro terrível, não há A / C, rádio ou assento. Na verdade, as tábuas do piso também estão faltando - você só precisa se segurar no volante e esperar o melhor!)
Então, qual dessas classes precisa de uma interface? A resposta pode ser todas elas, ou nenhuma delas - dependendo do seu design.
Você poderia ter uma interface parecida com esta:
Interface ICabin
Event IgnitionSwitchTurnedOn()
Event IgnitionSwitchTurnedOff()
Event BrakePedalPositionChanged(int percent)
Event GasPedalPositionChanged(int percent)
Event GearShiftGearChanged(int gearNum)
Event SteeringWheelTurned(float degree)
End Interface
Nesse ponto, o comportamento dessas classes se torna parte da interface / API do ICabin. Neste exemplo, as classes (se houver alguma) são provavelmente simples, com algumas propriedades e uma função ou duas. E o que você está dizendo implicitamente com seu design é que essas classes existem apenas para apoiar qualquer implementação concreta do ICabin que você tenha, e elas não podem existir por si mesmas ou são sem sentido fora do contexto da ICabin.
É o mesmo motivo pelo qual você não faz teste de unidade de membros particulares - eles existem apenas para oferecer suporte à API pública e, portanto, seu comportamento deve ser testado testando a API.
Portanto, se sua classe existe apenas para oferecer suporte a outra classe, e conceitualmente você a vê como realmente não tendo seu próprio domínio, não há problema em ignorar a interface. Mas se sua classe é importante o suficiente para que você considere que ela cresceu o suficiente para ter seu próprio domínio, vá em frente e faça uma interface.
EDITAR:
Frequentemente (incluindo nesta resposta), você lerá coisas como 'domínio', 'dependência' (frequentemente acopladas à 'injeção') que não significam nada para você quando você está começando a programar (elas com certeza não significa qualquer coisa para mim). Para domínio, significa exatamente o que parece:
O território sobre o qual domínio ou autoridade é exercido; as posses de uma soberana ou comunidade, ou algo semelhante. Também usado figurativamente. [WordNet sense 2] [1913 Webster]
Nos termos do meu exemplo - vamos considerar o IgnitionSwitch
. Em um carro com espaço para carnes, o interruptor de ignição é responsável por:
Essas propriedades compõem o domínio do IgnitionSwitch
, ou em outras palavras, o que ele conhece e é responsável.
O IgnitionSwitch
não é responsável pelo GasPedal
. O interruptor de ignição é completamente ignorante do pedal do acelerador em todos os sentidos. Os dois operam completamente independentes um do outro (embora um carro não tenha valor algum sem os dois!).
Como afirmei originalmente, isso depende do seu design. Você pode criar um IgnitionSwitch
que tenha dois valores: Ativado ( True
) e Desativado ( False
). Ou você pode projetá-lo para autenticar a chave fornecida e uma série de outras ações. Essa é a parte mais difícil de ser desenvolvedor: decidir onde traçar as linhas na areia - e honestamente na maioria das vezes é completamente relativo. Porém, essas linhas na areia são importantes - é onde está sua API e, portanto, onde devem estar suas interfaces.
Do MSDN :
As interfaces são mais adequadas para situações em que seus aplicativos requerem muitos tipos de objetos possivelmente não relacionados para fornecer determinadas funcionalidades.
As interfaces são mais flexíveis que as classes base, porque você pode definir uma única implementação que pode implementar várias interfaces.
As interfaces são melhores em situações nas quais você não precisa herdar a implementação de uma classe base.
As interfaces são úteis nos casos em que você não pode usar a herança de classe. Por exemplo, estruturas não podem herdar de classes, mas podem implementar interfaces.
Geralmente, no caso de uma única classe, não será necessário implementar uma interface, mas, considerando o futuro do seu projeto, pode ser útil definir formalmente o comportamento necessário das classes.
Para responder à pergunta: Há mais do que isso.
Um aspecto importante de uma interface é a intenção.
Uma interface é "um tipo abstrato que não contém dados, mas expõe comportamentos" - Interface (computação) Portanto, se esse é um comportamento ou um conjunto de comportamentos que a classe suporta, então uma interface provavelmente é o padrão correto. Se, no entanto, o (s) comportamento (s) é (são) intrínseco (s) ao conceito incorporado pela classe, é provável que você não deseje uma interface.
A primeira pergunta a fazer é qual é a natureza da coisa ou processo que você está tentando representar. Em seguida, prossiga com as razões práticas para implementar essa natureza de uma determinada maneira.
Desde que você fez essa pergunta, suponho que você já tenha visto o interesse de ter uma interface oculta várias implementações. Isso pode ser manifestado pelo princípio de inversão de dependência.
No entanto, a necessidade de ter ou não uma interface não depende do número de suas implementações. O verdadeiro papel de uma interface é que ela define um contrato informando qual serviço deve ser fornecido, em vez de como deve ser implementado.
Uma vez definido o contrato, duas ou mais equipes podem trabalhar de forma independente. Digamos que você esteja trabalhando em um módulo A e dependa do módulo B, o fato de criar uma interface em B permite continuar seu trabalho sem se preocupar com a implementação de B, porque todos os detalhes estão ocultos pela interface. Assim, a programação distribuída se torna possível.
Embora o módulo B tenha apenas uma implementação de sua interface, a interface ainda é necessária.
Em conclusão, uma interface oculta detalhes de implementação de seus usuários. A programação da interface ajuda a escrever mais documentos, pois é necessário definir um contrato, escrever um software mais modular, promover testes de unidade e acelerar a velocidade de desenvolvimento.
Todas as respostas aqui são muito boas. Na verdade, na maioria das vezes você não precisa implementar uma interface diferente. Mas há casos em que você pode querer fazê-lo de qualquer maneira. Aqui está um caso em que eu faço:
A classe implementa outra interface que eu não quero expor
Happen frequentemente com a classe do adaptador que une o código de terceiros.
interface NameChangeListener { // Implemented by a lot of people
void nameChanged(String name);
}
interface NameChangeCount { // Only implemented by my class
int getCount();
}
class NameChangeCounter implements NameChangeListener, NameChangeCount {
...
}
class SomeUserInterface {
private NameChangeCount currentCount; // Will never know that you can change the counter
}
A classe usa uma tecnologia específica que não deve vazar na
maioria das vezes ao interagir com bibliotecas externas. Mesmo se houver apenas uma implementação, eu uso uma interface para garantir que eu não introduza acoplamentos desnecessários com a biblioteca externa.
interface SomeRepository { // Guarantee that the external library details won't leak trough
...
}
class OracleSomeRepository implements SomeRepository {
... // Oracle prefix allow us to quickly know what is going on in this class
}
Comunicação entre camadas
Mesmo que apenas uma classe de interface do usuário implemente uma da classe de domínio, ela permite uma melhor separação entre essas camadas e, o mais importante, evita a dependência cíclica.
package project.domain;
interface UserRequestSource {
public UserRequest getLastRequest();
}
class UserBehaviorAnalyser {
private UserRequestSource requestSource;
}
package project.ui;
class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
// UI concern, no need for my domain object to know about this method.
public void displayLabelInErrorMode();
// They most certainly need to know about *that* though
public UserRequest getLastRequest();
}
Somente um subconjunto do método deve estar disponível para a maioria dos objetos.
Principalmente acontece quando tenho algum método de configuração na classe concreta
interface Sender {
void sendMessage(Message message)
}
class PacketSender implements Sender {
void sendMessage(Message message);
void setPacketSize(int sizeInByte);
}
class Throttler { // This class need to have full access to the object
private PacketSender sender;
public useLowNetworkUsageMode() {
sender.setPacketSize(LOW_PACKET_SIZE);
sender.sendMessage(new NotifyLowNetworkUsageMessage());
... // Other details
}
}
class MailOrder { // Not this one though
private Sender sender;
}
Portanto, no final, uso a interface pelo mesmo motivo que uso o campo privado: outro objeto não deve ter acesso a coisas que não deve acessar. Se eu tiver um caso como esse, apresento uma interface mesmo que apenas uma classe a implemente.
As interfaces são realmente importantes, mas tente controlar o número delas que você possui.
Depois de criar interfaces para praticamente tudo, é fácil acabar com o código de 'espaguete picado'. Apresento a maior sabedoria de Ayende Rahien, que publicou algumas palavras muito sábias sobre o assunto:
http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application
Este é o primeiro post de uma série inteira, então continue lendo!
Um motivo pelo qual você ainda pode querer introduzir uma interface nesse caso é seguir o Princípio de Inversão de Dependência . Ou seja, o módulo que usa a classe dependerá de uma abstração (ou seja, da interface) em vez de depender de uma implementação concreta. Separa os componentes de alto nível dos componentes de baixo nível.
Não há motivo real para fazer qualquer coisa. As interfaces são para ajudá-lo e não o programa de saída. Portanto, mesmo que a Interface seja implementada por um milhão de classes, não há regra que diga que você deve criar uma. Você cria um para que, quando você ou qualquer outra pessoa que use seu código, deseje alterar algo que se aplica a todas as implementações. Criar uma interface ajudará você em todos os casos futuros em que você queira criar outra classe que a implemente.
Nem sempre é necessário definir uma interface para uma classe.
Objetos simples, como objetos de valor, não têm várias implementações. Eles também não precisam ser ridicularizados. A implementação pode ser testada por si só e quando outras classes são testadas que dependem delas, o objeto de valor real pode ser usado.
Lembre-se de que criar uma interface tem um custo. Ele precisa ser atualizado ao longo da implementação, precisa de um arquivo extra e algum IDE terá problemas ao aplicar zoom na implementação, não na interface.
Então, eu definiria interfaces apenas para classes de nível superior, nas quais você deseja uma abstração da implementação.
Observe que com uma classe você obtém uma interface gratuitamente. Além da implementação, uma classe define uma interface do conjunto de métodos públicos. Essa interface é implementada por todas as classes derivadas. Não é estritamente falando uma interface, mas pode ser usada exatamente da mesma maneira. Portanto, não creio que seja necessário recriar uma interface que já exista sob o nome da classe.