O que significa “programar para uma interface”?

814

Já vi isso mencionado algumas vezes e não sei ao certo o que isso significa. Quando e por que você faria isso?

Eu sei o que as interfaces fazem, mas o fato de eu não estar claro sobre isso me faz pensar que estou perdendo o uso delas.

Será que você deveria fazer:

IInterface classRef = new ObjectWhatever()

Você poderia usar qualquer classe que implementa IInterface? Quando você precisaria fazer isso? A única coisa em que consigo pensar é se você tem um método e não tem certeza de qual objeto será passado, exceto pela implementação IInterface. Não consigo pensar quantas vezes você precisaria fazer isso.

Além disso, como você pode escrever um método que utiliza um objeto que implementa uma interface? Isso é possível?

Damien
fonte
3
Se você se lembra e seu programa precisa ser o ideal, antes da compilação, você pode trocar a declaração da Interface pela implementação real. O uso de uma interface adiciona um nível de indireção que causa um impacto no desempenho. Distribua seu código programado para interfaces embora ...
Ande Turner
18
@ Andrew Turner: isso é um péssimo conselho. 1) "seu programa precisa ser ideal" não é um bom motivo para trocar de interface! Então você diz "Distribua seu código programado para interfaces embora ...", então você está aconselhando que, dado o requisito (1), você libera código abaixo do ideal?!?
Mitch Wheat
74
A maioria das respostas aqui não está correta. Isso não significa ou mesmo implica "usar a palavra-chave da interface". Uma interface é uma especificação de como usar algo - sinônimo do contrato (procure). Separada disso, está a implementação, que é como esse contrato é cumprido. Programe apenas as garantias do método / tipo para que, quando o método / tipo for alterado de uma maneira que ainda obedeça ao contrato, ele não quebre o código usando-o.
precisa saber é o seguinte
2
@ apollodude217 que é realmente a melhor resposta em toda a página. Pelo menos para a questão no título, uma vez que existem pelo menos 3 questões bem diferentes aqui ...
Andrew Spencer
4
O problema fundamental com perguntas como essa é que pressupõe que "programar para uma interface" significa "agrupar tudo em uma interface abstrata", o que é tolo se você considerar que o termo antecede o conceito de interfaces abstratas no estilo Java.
11137 Jonathan Allen

Respostas:

1634

Há aqui algumas respostas maravilhosas para essas perguntas que abordam todos os tipos de detalhes sobre interfaces e código de acoplamento fraco, inversão de controle e assim por diante. Há algumas discussões bastante inebriantes, então eu gostaria de aproveitar um pouco as coisas para entender por que uma interface é útil.

Quando comecei a me expor a interfaces, eu também fiquei confuso sobre a relevância delas. Não entendi por que você precisava deles. Se estamos usando uma linguagem como Java ou C #, já temos herança e eu via as interfaces como uma forma mais fraca de herança e pensamento: "por que se preocupar?" Em certo sentido, eu estava certo, você pode pensar em interfaces como uma espécie de forma fraca de herança, mas além disso, finalmente entendi seu uso como uma construção de linguagem, pensando nelas como um meio de classificar traços ou comportamentos comuns exibidos por potencialmente muitas classes de objetos não relacionados.

Por exemplo - digamos que você tenha um jogo SIM e tenha as seguintes classes:

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

Claramente, esses dois objetos não têm nada em comum em termos de herança direta. Mas, você poderia dizer que ambos são irritantes.

Digamos que nosso jogo precise ter algum tipo de coisa aleatória que incomode o jogador quando ele janta. Pode ser um HouseFlyou um Telemarketerou ambos - mas como você permite os dois com uma única função? E como você pede a cada tipo diferente de objeto para "fazer sua coisa irritante" da mesma maneira?

A chave para perceber é que tanto um Telemarketere HouseFlycompartilham um comum vagamente interpretado comportamento, mesmo que eles não são nada parecidos em termos de modelagem-los. Então, vamos criar uma interface que ambos possam implementar:

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

Agora temos duas classes que podem ser irritantes à sua maneira. E eles não precisam derivar da mesma classe base e compartilhar características inerentes comuns - eles simplesmente precisam satisfazer o contrato IPest- esse contrato é simples. Você apenas precisa BeAnnoying. Nesse sentido, podemos modelar o seguinte:

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

Aqui, temos uma sala de jantar que aceita vários comensais e várias pragas - observe o uso da interface. Isso significa que, em nosso pequeno mundo, um membro da pestsmatriz pode realmente ser um Telemarketerobjeto ou um HouseFlyobjeto.

O ServeDinnermétodo é chamado quando o jantar é servido e nosso pessoal na sala de jantar deve comer. Em nosso pequeno jogo, é quando nossas pragas fazem seu trabalho - cada praga é instruída a ser irritante por meio da IPestinterface. Dessa forma, podemos facilmente ter os dois Telemarketerse HouseFlysser irritante em cada um de seus modos - nos preocupamos apenas com o fato de termos algo no DiningRoomobjeto que é uma praga, na verdade não nos importamos com o que é e eles não podem ter nada a ver. comum com outro.

Este exemplo pseudo-código muito elaborado (que se arrastou muito mais do que eu previa) serve apenas para ilustrar o tipo de coisa que finalmente acendeu a luz para mim em termos de quando poderíamos usar uma interface. Peço desculpas antecipadamente pela tolice do exemplo, mas espero que ajude na sua compreensão. E, com certeza, as outras respostas postadas que você recebeu aqui realmente cobrem toda a gama de uso de interfaces hoje em dia em padrões de design e metodologias de desenvolvimento.

Peter Meyer
fonte
3
Outra coisa a considerar é que, em alguns casos, pode ser útil ter uma interface para coisas que "podem" ser irritantes e ter uma variedade de objetos implementados BeAnnoyingcomo não operacionais; essa interface pode existir no lugar ou além da interface para coisas que são irritantes (se ambas as interfaces existirem, a interface "coisas que são irritantes" deve herdar da interface "coisas que podem ser irritantes"). A desvantagem de usar essas interfaces é que as implementações podem ficar sobrecarregadas com a implementação de um número "irritante" de métodos de stub. A vantagem é que ...
supercat
4
Os métodos não se destinam a representar métodos abstratos - sua implementação é irrelevante para a questão que se concentra nas interfaces.
Peter Meyer
33
Comportamentos encapsulando, como IPest é conhecido como o padrão de estratégia para o caso de alguém estiver interessado em seguir com mais material sobre o assunto ...
nckbrz
9
Curiosamente, você não aponta que, como os objetos nas IPest[]referências IPest, você pode chamar BeAnnoying()porque eles têm esse método, enquanto você não pode chamar outros métodos sem uma conversão. No entanto, cada objeto individualBeAnnoying() método será chamado.
D. Ben Knoble
4
Explicação muito boa ... Eu só preciso dizer aqui: nunca ouvi falar de interfaces sendo algum tipo de mecanismo de herança frouxa, mas, em vez disso, sei que a herança é usada como um mecanismo ruim para definir interfaces (por exemplo, em Python regular, você faça isso o tempo todo).
Carlos H Romano
283

O exemplo específico que eu costumava dar aos alunos é que eles deveriam escrever

List myList = new ArrayList(); // programming to the List interface

ao invés de

ArrayList myList = new ArrayList(); // this is bad

Eles parecem exatamente iguais em um programa curto, mas se você continuar usando myList 100 vezes no programa, poderá começar a ver a diferença. A primeira declaração garante que você chame apenas métodos myListdefinidos pela Listinterface (portanto, nenhum ArrayListmétodo específico). Se você programou a interface dessa maneira, mais tarde poderá decidir que realmente precisa

List myList = new TreeList();

e você só precisa alterar seu código nesse local. Você já sabe que o restante do seu código não faz nada que será quebrado alterando a implementação porque você programou para a interface .

Os benefícios são ainda mais óbvios (eu acho) quando você está falando sobre parâmetros de método e valores de retorno. Veja isso por exemplo:

public ArrayList doSomething(HashMap map);

Essa declaração de método vincula você a duas implementações concretas (ArrayList e HashMap). Assim que esse método é chamado a partir de outro código, qualquer alteração nesses tipos provavelmente significa que você também precisará alterar o código de chamada. Seria melhor programar para as interfaces.

public List doSomething(Map map);

Agora, não importa que tipo de Listretorno você retorne ou como Mapé transmitido como parâmetro. As alterações feitas dentro do doSomethingmétodo não forçarão a alteração do código de chamada.

Bill the Lizard
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Yvette
Explicação muito clara. Muito útil
samuel luswata
Eu tenho uma pergunta sobre o motivo que você mencionou "A primeira declaração garante que você chame apenas métodos em myList que são definidos pela interface List (portanto, nenhum método específico ArrayList). Se você programou a interface dessa maneira, mais tarde você pode decidir que você realmente precisa de List myList = new TreeList (); e você só precisa alterar seu código nesse local. " Talvez eu tenha entendido errado, eu me pergunto por que você precisa alterar ArrayList para TreeList se quiser "garantir que você chame apenas métodos em myList"?
user3014901
1
@ user3014901 Há vários motivos pelos quais você pode querer alterar o tipo de lista que está usando. Pode-se ter um melhor desempenho de pesquisa, por exemplo. O ponto é que, se você programar para a interface de Lista, será mais fácil alterar seu código para uma implementação diferente posteriormente.
Bill o Lagarto
73

Programar em uma interface está dizendo: "Eu preciso dessa funcionalidade e não me importo de onde ela vem".

Considere (em Java), a Listinterface versus as classes ArrayListe LinkedListconcretas. Se tudo o que me interessa é o fato de ter uma estrutura de dados que contém vários itens de dados que devo acessar por meio de iteração, eu selecionaria um List(e isso é 99% do tempo). Se eu souber que preciso inserir / excluir em tempo constante em qualquer extremidade da lista, posso escolher a LinkedListimplementação concreta (ou, mais provavelmente, usar a interface Queue ). Se eu sei que preciso de acesso aleatório por índice, eu escolho a ArrayListclasse concreta.

kdgregory
fonte
1
concordo totalmente, ou seja, a independência entre o que é feito e o que é feito. Ao particionar um sistema junto componentes independentes, você acabar com um sistema que é simples e reutilizável (veja simples Made Easy pela cara que criou Clojure)
beluchin
38

O uso de interfaces é um fator-chave para tornar seu código facilmente testável, além de remover acoplamentos desnecessários entre suas classes. Ao criar uma interface que define as operações em sua classe, você permite que as classes que desejam usar essa funcionalidade possam utilizá-la sem depender diretamente da classe de implementação. Se, posteriormente, você decidir alterar e usar uma implementação diferente, precisará alterar apenas a parte do código em que a implementação é instanciada. O restante do código não precisa ser alterado porque depende da interface, não da classe de implementação.

Isso é muito útil na criação de testes de unidade. Na classe em teste, você depende da interface e injeta uma instância da interface na classe (ou uma fábrica que permita criar instâncias da interface conforme necessário) por meio do construtor ou de um configurador de propriedades. A classe usa a interface fornecida (ou criada) em seus métodos. Ao escrever seus testes, você pode simular ou falsificar a interface e fornecer uma interface que responda com os dados configurados no seu teste de unidade. Você pode fazer isso porque sua classe em teste lida apenas com a interface, não com sua implementação concreta. Qualquer classe que implemente a interface, incluindo sua classe simulada ou falsa, fará isso.

Edição: Abaixo está um link para um artigo onde Erich Gamma discute sua citação, "Programa para uma interface, não uma implementação".

http://www.artima.com/lejava/articles/designprinciples.html

tvanfosson
fonte
3
Por favor, leia novamente esta entrevista: Gamma, é claro, estava falando sobre o conceito de interface OO, não sobre o tipo especial de classe JAVA ou C # (ISomething). O problema é que a maioria das pessoas, embora ele estivesse falando sobre a palavra-chave, agora temos muitas interfaces desnecessárias (ISomething).
Sylvain Rodrigue
Muito boa entrevista. Por favor, tenha cuidado com futuros leitores, há quatro páginas na entrevista. Eu quase fecharia o navegador antes de vê-lo.
Ad Infinitum
38

A programação para uma interface não tem absolutamente nada a ver com interfaces abstratas, como vemos em Java ou .NET. Nem sequer é um conceito de POO.

O que isso significa é não mexer com as partes internas de um objeto ou estrutura de dados. Use a Interface do programa abstrata, ou API, para interagir com seus dados. Em Java ou C #, isso significa usar propriedades e métodos públicos em vez do acesso ao campo bruto. Para C, isso significa usar funções em vez de ponteiros brutos.

EDIT: E com bancos de dados, significa usar visualizações e procedimentos armazenados em vez de acesso direto à tabela.

Jonathan Allen
fonte
5
Melhor resposta. A gama fornece uma explicação semelhante aqui: artima.com/lejava/articles/designprinciples.html (na página 2). Ele está se referindo ao conceito OO, mas você está certo: é maior que isso.
Sylvain Rodrigue
36

Você deve analisar a Inversão de controle:

Nesse cenário, você não escreveria isso:

IInterface classRef = new ObjectWhatever();

Você escreveria algo como isto:

IInterface classRef = container.Resolve<IInterface>();

Isso entraria em uma configuração baseada em regras no containerobjeto e construiria o objeto real para você, que poderia ser ObjectWhatever. O importante é que você poderia substituir essa regra por algo que usasse outro tipo de objeto, e seu código ainda funcionaria.

Se deixarmos a IoC fora da mesa, você poderá escrever um código que saiba que pode falar com um objeto que faz algo específico , mas não com qual tipo de objeto ou como ele o faz.

Isso seria útil ao passar parâmetros.

Quanto à sua pergunta entre parênteses "Além disso, como você pode escrever um método que utiliza um objeto que implementa uma Interface? Isso é possível?", Em C # você simplesmente usava o tipo de interface para o tipo de parâmetro, assim:

public void DoSomethingToAnObject(IInterface whatever) { ... }

Isso se conecta diretamente à "conversa com um objeto que faz algo específico". O método definido acima sabe o que esperar do objeto, que implementa tudo na interface II, mas não se importa com o tipo de objeto, apenas com o cumprimento do contrato, o que é uma interface.

Por exemplo, você provavelmente está familiarizado com calculadoras e provavelmente usou algumas em seus dias, mas na maioria das vezes elas são todas diferentes. Você, por outro lado, sabe como uma calculadora padrão deve funcionar, para poder usá-las todas, mesmo que não possa usar os recursos específicos que cada calculadora possui e que nenhum outro possui.

Essa é a beleza das interfaces. Você pode escrever um pedaço de código, que saiba que ele passará objetos para os quais pode esperar um determinado comportamento. Não importa que tipo de objeto seja, apenas que apóia o comportamento necessário.

Deixe-me dar um exemplo concreto.

Temos um sistema de tradução personalizado para formulários do Windows. Esse sistema percorre os controles em um formulário e traduz o texto em cada um. O sistema sabe como lidar com controles básicos, como o tipo de controle que possui uma propriedade de texto e itens básicos semelhantes, mas, para qualquer coisa básica, fica aquém.

Agora, como os controles herdam de classes predefinidas sobre as quais não temos controle, podemos fazer uma de três coisas:

  1. Crie suporte para o nosso sistema de tradução para detectar especificamente com que tipo de controle ele está trabalhando e converter os bits corretos (pesadelo de manutenção)
  2. Construir suporte em classes base (impossível, pois todos os controles herdam de diferentes classes predefinidas)
  3. Adicionar suporte de interface

Então fizemos o nr. 3. Todos os nossos controles implementam ILocalizable, que é uma interface que nos dá um método, a capacidade de traduzir "a si próprio" em um contêiner de regras / textos de tradução. Como tal, o formulário não precisa saber que tipo de controle encontrou, apenas que implementa a interface específica e sabe que existe um método no qual ele pode chamar para localizar o controle.

Lasse V. Karlsen
fonte
31
Por que mencionar a IoC no início, pois isso apenas acrescentaria mais confusão.
Kevin Le - Khnle
1
Concordo, eu diria que programar contra interfaces é apenas uma técnica para tornar a IoC mais fácil e confiável.
terjetyl
28

Código para a interface A implementação não tem nada a ver com Java, nem sua construção de interface.

Esse conceito foi destacado nos livros Patterns / Gang of Four, mas provavelmente já existia muito antes disso. O conceito certamente existia muito antes de o Java existir.

A construção da interface Java foi criada para ajudar nessa idéia (entre outras coisas), e as pessoas se concentraram demais na construção como o centro do significado e não a intenção original. No entanto, é por isso que temos métodos e atributos públicos e privados em Java, C ++, C #, etc.

Significa apenas interagir com um objeto ou interface pública do sistema. Não se preocupe ou até preveja como ele faz o que faz internamente. Não se preocupe sobre como é implementado. No código orientado a objetos, é por isso que temos métodos / atributos públicos versus privados. Nosso objetivo é usar os métodos públicos porque os métodos privados existem apenas para uso interno, dentro da classe. Eles compõem a implementação da classe e podem ser alterados conforme necessário, sem alterar a interface pública. Suponha que, em relação à funcionalidade, um método em uma classe execute a mesma operação com o mesmo resultado esperado toda vez que você o chamar com os mesmos parâmetros. Ele permite que o autor mude como a classe funciona, sua implementação, sem interromper a maneira como as pessoas interagem com ela.

E você pode programar para a interface, não para a implementação sem nunca usar uma construção de Interface. Você pode programar para a interface e não a implementação em C ++, que não possui uma construção de Interface. É possível integrar dois sistemas corporativos maciços com muito mais robustez, desde que eles interajam por meio de interfaces públicas (contratos), em vez de chamar métodos em objetos internos aos sistemas. Espera-se que as interfaces reajam sempre da mesma maneira esperada, considerando os mesmos parâmetros de entrada; se implementado na interface e não na implementação. O conceito funciona em muitos lugares.

Agite o pensamento de que as interfaces Java têm algo a ver com o conceito de 'Programa para a interface, não a implementação'. Eles podem ajudar a aplicar o conceito, mas eles são não o conceito.

Bill Rosmus
fonte
1
A primeira frase diz tudo. Essa deve ser a resposta aceita.
precisa saber é o seguinte
14

Parece que você entende como as interfaces funcionam, mas não tem certeza de quando usá-las e quais vantagens elas oferecem. Aqui estão alguns exemplos de quando uma interface faria sentido:

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

então eu poderia criar GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider, etc.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

em seguida, crie JpegImageLoader, GifImageLoader, PngImageLoader etc.

A maioria dos sistemas de suplementos e plugins funciona fora de interfaces.

Outro uso popular é para o padrão de repositório. Digamos que eu queira carregar uma lista de códigos postais de diferentes fontes

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

então eu poderia criar um XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository, etc. Para meus aplicativos Web, costumo criar repositórios XML no início, para que eu possa colocar algo em funcionamento antes que o banco de dados SQL esteja pronto. Quando o banco de dados estiver pronto, escrevo um SQLRepository para substituir a versão XML. O restante do meu código permanece inalterado, pois é executado apenas fora das interfaces.

Os métodos podem aceitar interfaces como:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}
Todd Smith
fonte
10

Isso torna seu código muito mais extensível e fácil de manter quando você tem conjuntos de classes semelhantes. Como programador júnior, não sou especialista, mas acabei de terminar um projeto que exigia algo semelhante.

Trabalho no software do cliente que fala com um servidor executando um dispositivo médico. Estamos desenvolvendo uma nova versão deste dispositivo que possui alguns novos componentes que o cliente deve configurar às vezes. Existem dois tipos de novos componentes, e eles são diferentes, mas também são muito semelhantes. Basicamente, eu tive que criar dois formulários de configuração, duas classes de listas, duas de tudo.

Decidi que seria melhor criar uma classe base abstrata para cada tipo de controle que contenha quase toda a lógica real e, em seguida, os tipos derivados para cuidar das diferenças entre os dois componentes. No entanto, as classes base não seriam capazes de executar operações nesses componentes se eu tivesse que me preocupar com tipos o tempo todo (bem, eles poderiam ter, mas haveria uma instrução "if" ou alternaria em todos os métodos) .

Eu defini uma interface simples para esses componentes e todas as classes base conversam com essa interface. Agora, quando troco de algo, praticamente 'funciona' em todos os lugares e não tenho duplicação de código.

Ed S.
fonte
10

Muitas explicações por aí, mas para tornar ainda mais simples. Tomemos, por exemplo, a List. Pode-se implementar uma lista com:

  1. Uma matriz interna
  2. Uma lista vinculada
  3. Outras implementações

Ao criar uma interface, diga a List. Você codifica apenas quanto à definição da lista ou o que Listsignifica na realidade.

Você pode usar qualquer tipo de implementação internamente, como uma arrayimplementação. Mas suponha que você queira alterar a implementação por algum motivo, digamos, um bug ou desempenho. Então você só precisa alterar a declaração List<String> ls = new ArrayList<String>()para List<String> ls = new LinkedList<String>().

Em nenhum outro lugar no código, você terá que alterar qualquer outra coisa; Porque todo o resto foi construído sobre a definição de List.

Jatin
fonte
8

Se você programar em Java, o JDBC é um bom exemplo. O JDBC define um conjunto de interfaces, mas não diz nada sobre a implementação. Seus aplicativos podem ser escritos nesse conjunto de interfaces. Em teoria, você escolhe algum driver JDBC e seu aplicativo funcionaria. Se você descobrir que há um driver JDBC mais rápido, "melhor" ou mais barato ou por qualquer motivo, em teoria, você poderá reconfigurar seu arquivo de propriedades e, sem precisar fazer nenhuma alteração no aplicativo, o aplicativo continuará funcionando.

Kevin Le - Khnle
fonte
Não é apenas útil no caso de um driver melhor se tornar disponível, pois é possível alterar completamente os fornecedores do banco de dados.
Ken Liu
3
O JDBC é tão ruim que precisa ser substituído. Encontre outro exemplo.
Joshua Joshua
O JDBC é ruim, mas não por qualquer motivo relacionado à interface versus implementação ou níveis de abstração. E assim, para ilustrar o conceito em questão, é simplesmente perfeito.
Erwin Smout 12/03/19
8

Programar para interfaces é incrível, pois promove um acoplamento solto. Como o @lassevk mencionou, a Inversão de Controle é um ótimo uso disso.

Além disso, consulte os princípios do SOLID . aqui está uma série de vídeos

Ele passa por um código rígido (exemplo fortemente acoplado) e depois analisa as interfaces, finalmente progredindo para uma ferramenta IoC / DI (NInject)

dbones
fonte
7

Estou atrasado nesta questão, mas quero mencionar aqui que a linha "Programa para uma interface, não uma implementação" teve uma boa discussão no livro de Padrões de Design do GoF (Gang of Four).

Afirmou, na p. 18:

Programe para uma interface, não uma implementação

Não declare variáveis ​​como instâncias de classes concretas específicas. Em vez disso, confirme apenas uma interface definida por uma classe abstrata. Você descobrirá que esse é um tema comum dos padrões de design deste livro.

e acima disso, começou com:

Existem dois benefícios em manipular objetos apenas em termos da interface definida por classes abstratas:

  1. Os clientes continuam desconhecendo os tipos específicos de objetos que usam, desde que sigam a interface esperada pelos clientes.
  2. Os clientes continuam desconhecendo as classes que implementam esses objetos. Os clientes conhecem apenas as classes abstratas que definem a interface.

Portanto, em outras palavras, não escreva suas classes para que ele tenha um quack()método para patos e, em seguida, um bark()método para cães, porque eles são muito específicos para uma implementação específica de uma classe (ou subclasse). Em vez disso, escreva o método usando nomes suficientemente genéricos para serem usados ​​na classe base, como giveSound()or move(), para que possam ser usados ​​para patos, cães ou até carros, e então o cliente de sua classe poderá apenas dizer, em .giveSound()vez de pensando em usar quack()ou bark()até mesmo determinar o tipo antes de emitir a mensagem correta a ser enviada ao objeto.

falta de polaridade
fonte
6

Além da resposta já selecionada (e das várias postagens informativas aqui), eu recomendo pegar uma cópia do Head First Design Patterns . É uma leitura muito fácil e responderá sua pergunta diretamente, explicará por que é importante e mostrará muitos padrões de programação que você pode usar para fazer uso desse princípio (e outros).

baleia
fonte
5

Para adicionar às postagens existentes, às vezes a codificação para interfaces ajuda em grandes projetos quando os desenvolvedores trabalham em componentes separados simultaneamente. Tudo o que você precisa é definir interfaces com antecedência e gravar código nelas, enquanto outros desenvolvedores gravam código na interface que você está implementando.

e11s
fonte
4

Também é bom para o teste de unidade, você pode injetar suas próprias classes (que atendem aos requisitos da interface) em uma classe que depende dela

Richard
fonte
4

Pode ser vantajoso programar para interfaces, mesmo quando não dependemos de abstrações.

A programação para interfaces nos obriga a usar um subconjunto contextualmente apropriado de um objeto . Isso ajuda porque:

  1. nos impede de fazer coisas contextualmente inadequadas, e
  2. nos permite alterar com segurança a implementação no futuro.

Por exemplo, considere uma Personclasse que implementa Frienda Employeeinterface e.

class Person implements AbstractEmployee, AbstractFriend {
}

No contexto do aniversário da pessoa, programamos para a Friendinterface, para evitar tratar a pessoa como um Employee.

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

No contexto do trabalho da pessoa, programamos para a Employeeinterface, para evitar que os limites do local de trabalho sejam confusos.

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

Ótimo. Nos comportamos adequadamente em diferentes contextos e nosso software está funcionando bem.

No futuro, se nossa empresa mudar para trabalhar com cães, podemos mudar o software com bastante facilidade. Primeiro, criamos uma Dogclasse que implementa ambos Friende Employee. Então, nós mudamos com segurançanew Person() com para new Dog(). Mesmo se as duas funções tiverem milhares de linhas de código, essa edição simples funcionará porque sabemos o seguinte:

  1. A função partyusa apenas oFriend subconjunto de Person.
  2. A função workplaceusa apenas oEmployee subconjunto de Person.
  3. Classe Dogimplementa tanto o FriendeEmployee interfaces.

Por outro lado, se um partyou workplacedeveria ter sido programado Person, haveria o risco de ambos terem um Personcódigo específico. Mudar de Personpara Dogexigiria que vasculhassemos o código para extirpar qualquerPerson código específico que Dognão suporte.

A moral : programar para interfaces ajuda nosso código a se comportar adequadamente e a estar pronto para a mudança. Também prepara nosso código para depender de abstrações, o que traz ainda mais vantagens.

Shaun Luttin
fonte
1
Supondo que você não tenha interfaces excessivamente amplas, é isso.
Casey
4

Se eu estiver escrevendo uma nova classe Swimmerpara adicionar a funcionalidade swim()e precisar usar um objeto da classe digamos Dog, e essa Dogclasse implementa a interface Animalque declara swim().

No topo da hierarquia ( Animal), é muito abstrato, enquanto na parte inferior ( Dog) é muito concreto. A maneira como penso em "programar para interfaces" é que, enquanto escrevo Swimmerclasse, quero escrever meu código na interface que está na hierarquia que, neste caso, é um Animalobjeto. Uma interface está livre de detalhes de implementação e, portanto, torna seu código fracamente acoplado.

Os detalhes da implementação podem ser alterados com o tempo, no entanto, isso não afetaria o código restante, pois tudo com o qual você está interagindo é com a interface e não com a implementação. Você não se importa como é a implementação ... tudo o que você sabe é que haverá uma classe que implementaria a interface.

Abhishek Shrivastava
fonte
3

Portanto, apenas para acertar, a vantagem de uma interface é que eu posso separar a chamada de um método de qualquer classe específica. Em vez de criar uma instância da interface, em que a implementação é fornecida de qualquer classe que eu escolher que implementa essa interface. Assim, permitindo-me ter muitas classes, que têm funcionalidades semelhantes, mas ligeiramente diferentes, e em alguns casos (os casos relacionados à intenção da interface) não se importam com qual objeto.

Por exemplo, eu poderia ter uma interface de movimento. Um método que faz algo 'se mover' e qualquer objeto (Pessoa, Carro, Gato) que implementa a interface de movimento pode ser passado e instruído a se mover. Sem o método, todos sabem o tipo de classe que é.

Damien
fonte
3

Imagine que você tenha um produto chamado 'Zebra' que pode ser estendido por plugins. Ele encontra os plugins pesquisando DLLs em algum diretório. Ele carrega todas essas DLLs e usa reflexão para encontrar todas as classes que implementamIZebraPlugin e, em seguida, chama os métodos dessa interface para se comunicar com os plug-ins.

Isso o torna completamente independente de qualquer classe de plug-in específica - não se importa com o que são as classes. Só importa que eles atendam às especificações da interface.

As interfaces são uma maneira de definir pontos de extensibilidade como este. O código que fala com uma interface é mais fracamente acoplado - na verdade, não está acoplado a nenhum outro código específico. Ele pode interagir com plug-ins escritos anos depois por pessoas que nunca conheceram o desenvolvedor original.

Você poderia usar uma classe base com funções virtuais - todos os plugins seriam derivados da classe base. Mas isso é muito mais limitador porque uma classe pode ter apenas uma classe base, enquanto pode implementar qualquer número de interfaces.

Daniel Earwicker
fonte
3

Explicação C ++.

Pense em uma interface como seus métodos públicos de classes.

Você pode criar um modelo que 'depende' desses métodos públicos para executar sua própria função (faz chamadas de função definidas na interface pública das classes). Digamos que esse modelo seja um contêiner, como uma classe Vector, e a interface da qual ele depende é um algoritmo de pesquisa.

Qualquer classe de algoritmo que defina as funções / interfaces para as quais o vetor faz chamadas satisfará o 'contrato' (como alguém explicou na resposta original). Os algoritmos nem precisam ser da mesma classe base; o único requisito é que as funções / métodos dos quais o vetor depende (interface) sejam definidas no seu algoritmo.

O objetivo de tudo isso é que você poderia fornecer qualquer algoritmo / classe de pesquisa diferente, desde que ele fornecesse a interface da qual o Vector depende (pesquisa de bolhas, pesquisa seqüencial, pesquisa rápida).

Você também pode projetar outros contêineres (listas, filas) que usariam o mesmo algoritmo de pesquisa que Vector, fazendo com que eles cumprissem a interface / contrato do qual seus algoritmos de pesquisa dependem.

Isso economiza tempo (princípio de OOP 'reutilização de código'), pois você é capaz de escrever um algoritmo uma vez, em vez de uma e outra vez e de uma vez por outra específico para cada novo objeto que você cria, sem complicar demais o problema com uma árvore de herança crescida.

Quanto a 'perder' como as coisas funcionam; grande momento (pelo menos em C ++), pois é assim que a maioria da estrutura da Standard TEMPLATE Library opera.

Obviamente, ao usar classes de herança e abstratas, a metodologia de programação para uma interface muda; mas o princípio é o mesmo, suas funções / métodos públicos são sua interface de classes.

Este é um tópico enorme e um dos princípios fundamentais dos Design Patterns.

Trae Barlow
fonte
3

Em Java, essas classes concretas implementam a interface CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Essas classes concretas não têm uma classe pai comum que não seja Object, portanto, não há nada que as relacione, exceto o fato de que cada uma delas tem algo a ver com matrizes de caracteres, representando ou manipulando tais. Por exemplo, os caracteres de String não podem ser alterados depois que um objeto String é instanciado, enquanto os caracteres de StringBuffer ou StringBuilder podem ser editados.

No entanto, cada uma dessas classes é capaz de implementar adequadamente os métodos da interface CharSequence:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

Em alguns casos, as classes da biblioteca de classes Java que costumavam aceitar String foram revisadas para agora aceitar a interface CharSequence. Portanto, se você tiver uma instância do StringBuilder, em vez de extrair um objeto String (o que significa instanciar uma nova instância do objeto), poderá passar o próprio StringBuilder ao implementar a interface CharSequence.

A interface Appendable implementada por algumas classes tem o mesmo tipo de benefício para qualquer situação em que os caracteres possam ser anexados a uma instância da instância de objeto de classe concreta subjacente. Todas essas classes concretas implementam a interface Appendable:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer

RogerV
fonte
É muito ruim interfaces como CharSequencesão tão anêmicas. Eu gostaria que Java e .NET permitissem que interfaces tivessem implementação padrão, para que as pessoas não analisassem as interfaces apenas com o objetivo de minimizar o código padrão. Dada qualquer CharSequenceimplementação legítima, pode-se emular a maioria das funções Stringusando apenas os quatro métodos acima, mas muitas implementações podem executar essas funções com muito mais eficiência de outras maneiras. Infelizmente, mesmo que uma implementação específica de CharSequencetenha tudo em um único char[]e possa executar muitos ... #
226
... operações como indexOfrapidamente, não há como um chamador que não esteja familiarizado com uma implementação específica CharSequencepossa solicitar isso, em vez de precisar usar charAtpara examinar cada caractere individual.
supercat
3

Breve história: Um carteiro é solicitado a ir para casa depois de casa e receber as capas (cartas, documentos, cheques, cartões-presente, inscrição, carta de amor) com o endereço escrito para entregar.

Suponha que não haja cobertura e peça ao carteiro que volte para casa depois de casa e receba todas as coisas e entregue a outras pessoas, pois o carteiro pode ficar confuso.

Então é melhor envolvê-lo com capa (em nossa história, é a interface), para que ele faça seu trabalho bem.

Agora, o trabalho do carteiro é receber e entregar apenas as capas (ele não se incomodou com o que está dentro da capa).

Crie um tipo de tipo interfacenão real, mas implemente-o com o tipo real.

Criar interface significa que seus componentes se encaixam facilmente no restante do código

Eu te dou um exemplo.

você tem a interface AirPlane como abaixo.

interface Airplane{
    parkPlane();
    servicePlane();
}

Suponha que você tenha métodos em sua classe de planos Controller, como

parkPlane(Airplane plane)

e

servicePlane(Airplane plane)

implementado em seu programa. Não quebrará seu código. Quero dizer, ele não precisa mudar desde que aceite argumentos como AirPlane.

Porque ele vai aceitar qualquer avião apesar tipo real, flyer, highflyr, fighter, etc.

Além disso, em uma coleção:

List<Airplane> plane; // Vai pegar todos os seus aviões.

O exemplo a seguir irá esclarecer sua compreensão.


Você tem um avião de combate que o implementa, então

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

A mesma coisa para o HighFlyer e outras classes:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Agora pense em suas classes de controlador usando AirPlanevárias vezes,

Suponha que sua classe Controller seja ControlPlane, como abaixo,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

Aí vem a mágica, pois você pode fazer com que suas novas AirPlaneinstâncias de tipo sejam quantas quiser e não está alterando o código deControlPlane classe.

Você pode adicionar uma instância ...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Você também pode remover instâncias de tipos criados anteriormente.

Sanjay Rabari
fonte
2

Uma interface é como um contrato, em que você deseja que sua classe de implementação implemente métodos escritos no contrato (interface). Como o Java não fornece herança múltipla, "programar para interface" é uma boa maneira de obter herança múltipla.

Se você possui uma classe A que já está estendendo outra classe B, mas deseja que ela também siga certas diretrizes ou implemente um determinado contrato, poderá fazê-lo pela estratégia "programação para interface".

Shivam Sugandhi
fonte
2

P: - ... "Você poderia usar alguma classe que implemente uma interface?"
A: Sim.

P: - ... "Quando você precisaria fazer isso?"
R: - Sempre que você precisar de uma classe (s) que implemente interface (s).

Nota: Não foi possível instanciar uma interface não implementada por uma classe - True.

  • Por quê?
  • Como a interface possui apenas protótipos de métodos, não definições (apenas nomes de funções, não sua lógica)

AnIntf anInst = new Aclass();
// poderíamos fazer isso apenas se o Aclass implementasse o AnIntf.
// anInst terá referência Aclass.


Nota: Agora poderíamos entender o que aconteceu se Bclass e Cclass implementassem o mesmo Dintf.

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

O que temos: Os mesmos protótipos de interface (nomes de funções na interface) e chamam implementações diferentes.

Bibliografia: Protótipos - wikipedia

Meraj al Maksud
fonte
1

O programa para uma interface permite alterar a implementação do contrato definido pela interface sem problemas. Permite um acoplamento flexível entre o contrato e as implementações específicas.

IInterface classRef = new ObjectWhatever()

Você poderia usar qualquer classe que implemente IInterface? Quando você precisaria fazer isso?

Dê uma olhada nesta pergunta SE para um bom exemplo.

Por que a interface para uma classe Java deve ser preferida?

usar uma interface atinge desempenho?

Sendo assim, quanto?

Sim. Ele terá uma leve sobrecarga de desempenho em sub-segundos. Mas se o seu aplicativo precisar alterar dinamicamente a implementação da interface, não se preocupe com o impacto no desempenho.

como você pode evitá-lo sem ter que manter dois bits de código?

Não tente evitar várias implementações de interface se seu aplicativo precisar delas. Na ausência de acoplamento rígido da interface com uma implementação específica, talvez seja necessário implantar o patch para alterar uma implementação para outra implementação.

Um bom caso de uso: Implementação do padrão de estratégia:

Exemplo do mundo real do padrão de estratégia

Ravindra babu
fonte
1

programa para uma interface é um termo do livro do GOF. Eu não diria diretamente que tem a ver com a interface java, mas com interfaces reais. Para obter uma separação de camada limpa, é necessário criar uma separação entre os sistemas, por exemplo: digamos que você tenha um banco de dados concreto que deseja usar, nunca "programaria no banco de dados", mas sim "programaria na interface de armazenamento". Da mesma forma, você nunca "programa para um serviço da Web", mas sim para uma "interface do cliente". isso é para que você possa trocar facilmente as coisas.

Acho que essas regras me ajudam:

1 . usamos uma interface java quando temos vários tipos de um objeto. se eu tiver apenas um objeto, não entendo o ponto. se houver pelo menos duas implementações concretas de alguma idéia, eu usaria uma interface java.

2 . se, como afirmei acima, você deseja trazer a dissociação de um sistema externo (sistema de armazenamento) para o seu próprio sistema (banco de dados local), use também uma interface.

observe como existem duas maneiras de considerar quando usá-las. espero que isto ajude.

j2emanue
fonte
0

Também vejo muitas respostas boas e explicativas aqui, por isso quero dar meu ponto de vista aqui, incluindo algumas informações extras que eu notei ao usar esse método.

Teste de unidade

Nos últimos dois anos, escrevi um projeto de hobby e não escrevi testes de unidade para ele. Depois de escrever cerca de 50 mil linhas, descobri que seria realmente necessário escrever testes de unidade. Não usei interfaces (ou muito pouco) ... e quando fiz meu primeiro teste de unidade, descobri que era complicado. Por quê?

Porque eu tive que criar muitas instâncias de classe, usadas para entrada como variáveis ​​e / ou parâmetros de classe. Portanto, os testes se parecem mais com testes de integração (tendo que criar uma 'estrutura' completa de classes, pois tudo estava vinculado).

Medo de interfaces Então eu decidi usar interfaces. Meu medo era que eu tivesse que implementar todas as funcionalidades em todos os lugares (em todas as classes usadas) várias vezes. De alguma forma, isso é verdade, porém, usando a herança, pode-se reduzir muito.

Combinação de interfaces e herança Descobri que a combinação é muito boa de ser usada. Eu dou um exemplo muito simples.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

Dessa forma, a cópia do código não é necessária, enquanto ainda há o benefício de usar um carro como interface (ICar).

Michel Keijzers
fonte
0

Vamos começar com algumas definições primeiro:

Interface n. O conjunto de todas as assinaturas definidas pelas operações de um objeto é chamado de interface para o objeto

Digite n. Uma interface específica

Um exemplo simples de uma interface de , tal como definido acima seria todos os métodos DOP de objectos, tais como query(), commit(),close() etc., como um todo, não separadamente. Esses métodos, ou seja, sua interface definem o conjunto completo de mensagens, solicitações que podem ser enviadas ao objeto.

Um tipo como definido acima é uma interface específica. I vai utilizar a interface de forma feita para demonstrar-se: draw(), getArea(), getPerimeter()etc ..

Se um objeto é do tipo Banco de dados, significa que ele aceita mensagens / solicitações da interface do banco de dados query(), commit()etc. Os objetos podem ser de vários tipos. Você pode fazer com que um objeto de banco de dados seja do tipo de forma, desde que implemente sua interface; nesse caso, isso seria uma sub-digitação .

Muitos objetos podem ter muitas interfaces / tipos diferentes e implementar essa interface de maneira diferente. Isso nos permite substituir objetos, deixando-nos escolher qual deles usar. Também conhecido como polimorfismo.

O cliente só estará ciente da interface e não da implementação.

Portanto, em essência, programar para uma interface envolveria criar algum tipo de classe abstrata, como Shapea interface especificada apenas draw(), ou seja getCoordinates(), getArea()etc. E então, diferentes classes concretas implementam essas interfaces, como uma classe Circle, Square, Triangle. Daí o programa para uma interface, não uma implementação.

Robert Rocha
fonte
0

"Programa para interface" significa não fornecer código rígido da maneira certa, o que significa que seu código deve ser estendido sem interromper a funcionalidade anterior. Apenas extensões, não editando o código anterior.

ndoty
fonte