Eu me deparei com o termo "programação para uma interface em vez de uma implementação" e acho que meio que entendo o que isso significa. Mas quero ter certeza de entender seus benefícios e possíveis implementações.
"Programar para uma interface" significa que, quando possível, deve-se referir a um nível mais abstrato de uma classe (uma interface, classe abstrata ou, às vezes, uma superclasse de algum tipo), em vez de se referir a uma implementação concreta.
Um exemplo comum em Java é usar:
List myList = new ArrayList();
em vez de ArrayList myList = new ArrayList();
.
Eu tenho duas perguntas sobre isso:
Quero ter certeza de que entendo os principais benefícios dessa abordagem. Eu acho que os benefícios são principalmente flexibilidade. Declarar um objeto como uma referência de mais alto nível, em vez de uma implementação concreta, permite mais flexibilidade e facilidade de manutenção ao longo do ciclo de desenvolvimento e do código. Isso está correto? A flexibilidade é o principal benefício?
Existem mais maneiras de 'programar para uma interface'? Ou "declarar uma variável como uma interface e não como uma implementação concreta" é a única implementação desse conceito?
Eu não estou falando sobre a interface de construção Java . Estou falando do princípio OO "programação para uma interface, não uma implementação". Nesse princípio, a "interface" mundial refere-se a qualquer "supertipo" de uma classe - uma interface, uma classe abstrata ou uma superclasse simples que é mais abstrata e menos concreta do que suas subclasses mais concretas.
fonte
Respostas:
Isto não está correto . Ou pelo menos, não está totalmente correto.
O ponto mais importante vem da perspectiva do design do programa. Aqui, "programar para uma interface" significa focar seu design no que o código está fazendo, não como ele faz. Essa é uma distinção vital que leva seu design à correção e flexibilidade.
A idéia principal é que os domínios mudam muito mais lentamente que o software. Digamos que você tenha um software para acompanhar sua lista de compras. Nos anos 80, esse software funcionava contra uma linha de comando e alguns arquivos simples no disquete. Então você tem uma interface do usuário. Então você pode colocar a lista no banco de dados. Mais tarde, talvez tenha sido transferido para a nuvem ou para telefones celulares ou para a integração no Facebook.
Se você projetou seu código especificamente em torno da implementação (disquetes e linhas de comando), não estaria preparado para alterações. Se você projetou seu código em torno da interface (manipulando uma lista de compras), a implementação poderá sofrer alterações.
fonte
List myList = new ArrayList()
em vez deArrayList myList = new ArrayList()
. (A pergunta é no próximo comentário)List
NãoArrayList
é disso que estou falando. É mais como fornecer umEmployee
objeto do que o conjunto de tabelas vinculadas usadas para armazenar um registro de Funcionário. Ou fornecendo uma interface para percorrer as músicas e não se importando se essas músicas são embaralhadas, ou em um CD, ou transmitidas pela Internet. Eles são apenas uma sequência de músicas.Meu entendimento de "programação para uma interface" é diferente do que a pergunta ou as outras respostas sugerem. O que não quer dizer que meu entendimento esteja correto ou que as coisas nas outras respostas não sejam boas idéias, apenas que não são o que penso quando ouço esse termo.
Programar em uma interface significa que, quando lhe são apresentadas algumas interfaces de programação (seja uma biblioteca de classes, um conjunto de funções, um protocolo de rede ou qualquer outra coisa), você continua usando apenas as coisas garantidas pela interface. Você pode ter conhecimento sobre a implementação subjacente (pode ter escrito), mas nunca deve usá-lo.
Por exemplo, digamos que a API apresente algum valor opaco que é um "identificador" para algo interno. Seu conhecimento pode dizer que esse identificador é realmente um ponteiro, e você pode desreferenciá-lo e acessar algum valor, o que pode permitir que você realize facilmente alguma tarefa que deseja executar. Mas a interface não fornece essa opção; é o seu conhecimento da implementação específica que faz.
O problema disso é que ele cria um forte acoplamento entre seu código e a implementação, exatamente o que a interface deveria impedir. Dependendo da política, isso pode significar que a implementação não pode mais ser alterada, porque isso quebraria seu código ou que seu código é muito frágil e continua quebrando a cada atualização ou alteração da implementação subjacente.
Um grande exemplo disso são os programas criados para o Windows. O WinAPI é uma interface, mas muitas pessoas usaram truques que funcionaram devido à implementação específica, digamos, no Windows 95. Esses truques talvez tornaram seus programas mais rápidos ou permitiram que eles fizessem coisas com menos código do que seria necessário. Mas esses truques também significavam que o programa falharia no Windows 2000, porque a API foi implementada de maneira diferente. Se o programa fosse importante o suficiente, a Microsoft poderia realmente seguir em frente e adicionar alguns truques à sua implementação para que o programa continuasse a funcionar, mas o custo disso é maior complexidade (com todos os problemas resultantes) do código do Windows. Isso também torna a vida mais difícil para o pessoal do Wine, porque eles tentam implementar a WinAPI também, mas eles só podem se referir à documentação de como fazer isso,
fonte
Só posso falar da minha experiência pessoal, pois isso nunca me foi formalmente ensinado.
Seu primeiro ponto está correto. A flexibilidade ganha vem de não poder invocar acidentalmente detalhes de implementação da classe concreta onde eles não devem ser invocados.
Por exemplo, considere uma
ILogger
interface atualmente implementada como umaLogToEmailLogger
classe concreta . ALogToEmailLogger
classe expõe todos osILogger
métodos e propriedades, mas também possui uma propriedade específica da implementaçãosysAdminEmail
.Quando o seu criador de logs é usado em seu aplicativo, não deve ser a preocupação do código que está consumindo configurá-lo
sysAdminEmail
. Essa propriedade deve ser definida durante a configuração do criador de logs e deve ser ocultada do mundo.Se você estava codificando a implementação concreta, poderá definir acidentalmente a propriedade de implementação ao usar o criador de logs. Agora, o código do aplicativo está fortemente acoplado ao seu criador de logs, e a mudança para outro criador de logs exigirá primeiro desacoplar seu código do original.
Nesse sentido, a codificação para uma interface afrouxa o acoplamento .
Em relação ao seu segundo ponto: Outro motivo que vi para codificar para uma interface é reduzir a complexidade do código.
Por exemplo, imagine que eu tenha um jogo com as seguintes interfaces
I2DRenderable
,I3DRenderable
,IUpdateable
. Não é incomum que um único componente do jogo tenha conteúdo renderizável em 2D e 3D. Outros componentes podem ser apenas 2D e outros apenas 3D.Se a renderização 2D for feita por um módulo, faz sentido manter uma coleção de
I2DRenderable
s. Não importa se os objetos em sua coleção também sãoI3DRenderable
ou seIUpdateble
outros módulos serão responsáveis por tratar esses aspectos dos objetos.Armazenar os objetos renderizáveis como uma lista
I2DRenderable
mantém baixa a complexidade da classe de renderização. A lógica de renderização e atualização 3D não é uma preocupação e, portanto, esses aspectos dos objetos filhos podem e devem ser ignorados.Nesse sentido, a codificação para uma interface mantém a complexidade baixa, isolando as preocupações .
fonte
Talvez haja dois usos da palavra interface sendo usados aqui. A interface a que você está se referindo principalmente na sua pergunta é uma interface Java . Isso é especificamente um conceito Java, mais geralmente é uma interface de linguagem de programação.
Eu diria que programar para uma interface é um conceito mais amplo. As agora populares APIs REST disponíveis para muitos sites são outro exemplo do conceito mais amplo de programação para uma interface em um nível superior. Ao criar uma camada entre o funcionamento interno do seu código e o mundo externo (pessoas na Internet, outros programas, até outras partes do mesmo programa), você pode alterar qualquer coisa dentro do seu código, desde que não mude o que o mundo exterior está esperando, onde isso é definido por uma interface ou contrato que você pretende honrar.
Isso fornece a flexibilidade de refatorar seu código interno, sem precisar dizer todas as outras coisas que dependem de sua interface.
Isso também significa que seu código deve ser mais estável. Ao aderir à interface, você não deve quebrar o código de outras pessoas. Quando você realmente precisar alterar a interface, poderá liberar uma nova versão principal (1.abc a 2.xyz) da API, que sinaliza que há alterações na interface da nova versão.
Como o @Doval aponta nos comentários sobre esta resposta, também há localidade de erros. Acho que tudo isso se resume ao encapsulamento. Assim como você o usaria para objetos em um design orientado a objetos, esse conceito também é útil em um nível superior.
fonte
Uma analogia do mundo real pode ajudar:
A Rede elétrica conecta uma interface.
Sim; aquela coisa de três pinos na extremidade do cabo de alimentação da sua TV, rádio, aspirador de pó, máquina de lavar, etc.
Qualquer aparelho que possua um plugue principal (isto é, implementa a interface "possui um plugue de rede") pode ser tratado exatamente da mesma maneira; todos podem ser conectados a uma tomada e podem extrair energia dessa tomada.
O que cada aparelho individual faz é totalmente diferente. Você não vai muito longe limpando seus tapetes com a TV e a maioria das pessoas não assiste à máquina de lavar para se divertir. Mas todos esses aparelhos compartilham o comportamento comum de poderem ser conectados a uma tomada.
É isso que as Interfaces oferecem. Comportamentos
unificados que podem ser realizados por muitas Classes de Objetos diferentes, sem a necessidade das complicações da Herança.
fonte
O termo "programação para uma interface" está aberto a muita interpretação. Interface no desenvolvimento de software é uma palavra muito comum. Aqui está como eu explico o conceito para os desenvolvedores juniores que treinei ao longo dos anos.
Na arquitetura de software, há uma grande variedade de limites naturais. Exemplos comuns incluem
O que importa é que, quando esses limites naturais existem, eles são identificados e o contrato de como esse limite se comporta é especificado. Você testa seu software não se o "outro lado" se comporta, mas se suas interações correspondem à especificação.
As conseqüências disso são:
Embora muito disso possa estar relacionado a classes e interfaces, é importante perceber que também se refere a modelos de dados, protocolos de rede e, de uma maneira mais geral, trabalhando com vários desenvolvedores
fonte