Noções básicas sobre “programação para uma interface”


29

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:

  1. 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?

  2. 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.


possível duplicação de Por que as interfaces são úteis?
mosquito


1
Esta resposta fornece um exemplo fácil de entender programmers.stackexchange.com/a/314084/61852
Tulains Córdova

Respostas:


44

"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.

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.


Obrigado pela resposta. A julgar pelo que você escreveu, acho que entendo o que significa "programação para uma interface" e quais são seus benefícios. Mas eu tenho uma pergunta - O exemplo concreto mais comum para esse conceito é o seguinte: ao criar uma referência a um objeto, torne o tipo de referência o tipo de interface que esse objeto implementa (ou a superclasse que esse objeto herda), em vez de criar o tipo de referência o tipo de objeto. (Aka, List myList = new ArrayList()em vez de ArrayList myList = new ArrayList(). (A pergunta é no próximo comentário)
Aviv Cohn

Minha pergunta é: você pode me dar mais exemplos de lugares no código do mundo real onde o princípio "programação para uma interface" ocorre? Diferente do exemplo comum que descrevi no último comentário?
Aviv Cohn

6
NÃO . ListNão ArrayListé disso que estou falando. É mais como fornecer um Employeeobjeto 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.
Telastyn 14/03

Você diria que "programar em uma interface, não em uma implementação" é um princípio que expressa o princípio Abstraction OO?
Aviv Cohn

1
@AvivCohn Gostaria de sugerir o SpringFramework como um bom exemplo. Tudo no Spring pode ser aprimorado ou personalizado, criando sua própria representação de suas interfaces e o principal comportamento do Springs: um recurso continuará funcionando conforme o esperado ... Ou, no caso de personalizações, como você esperava. Programar em uma interface também é a melhor estratégia para projetar estruturas de integração . Mais uma vez, a primavera faz com sua integração com a primavera. Em tempo de design, a programação para uma interface é o que fazemos com a UML. Ou é o que eu faria. Me abstraio do modo como funciona e me concentro no que fazer.
Laiv

18

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,


Esse é um bom ponto, e ouço muito isso em certos contextos.
Telastyn 14/03

Eu entendo o seu ponto. Então, deixe-me ver se consigo adaptar o que você está dizendo à programação geral: digamos que tenho uma classe (classe A) que usa a funcionalidade da classe abstrata B. As classes C e D herdam a classe B - elas fornecem implementação concreta para o que é dito que a classe faz. Se a classe A usa diretamente as classes C ou D, isso é chamado de 'programação para uma implementação', o que não é uma solução muito flexível. Mas se a classe A usa uma referência à classe B, que pode ser configurada posteriormente para a implementação C ou D, ela torna as coisas mais flexíveis e sustentáveis. Isso está correto?
Aviv Cohn

Se isso estiver correto, minha pergunta é: existem exemplos mais concretos de 'programação para uma interface', além do exemplo comum 'usando uma referência de interface em vez de uma referência de classe concreta'?
Aviv Cohn

2
@AvivCohn Um pouco tarde nesta resposta, mas um exemplo concreto é a World Wide Web. Durante a guerra dos navegadores (era do IE 4), os sites foram escritos não para o que dizia alguma especificação, mas para as peculiaridades de alguns navegadores (Netscape ou IE). Isso basicamente foi programado para a implementação em vez da interface.
Sebastian Redl

9

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 ILoggerinterface atualmente implementada como uma LogToEmailLoggerclasse concreta . A LogToEmailLoggerclasse expõe todos os ILoggermétodos e propriedades, mas também possui uma propriedade específica da implementação sysAdminEmail.

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 I2DRenderables. Não importa se os objetos em sua coleção também são I3DRenderableou se IUpdatebleoutros módulos serão responsáveis ​​por tratar esses aspectos dos objetos.

Armazenar os objetos renderizáveis ​​como uma lista I2DRenderablemanté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 .


4

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.


1
Um benefício que geralmente é esquecido é a localidade dos erros. Digamos que você precise de um mapa e o implemente usando uma árvore binária. Para que isso funcione, as chaves precisam ter alguma ordem e é necessário manter o invariante de que as chaves "menores que" a chave do nó atual estão na subárvore esquerda, enquanto as que são "maiores que" estão ativadas a subárvore direita. Quando você oculta a implementação do Mapa atrás de uma interface, se uma pesquisa no Mapa der errado, você saberá que o bug deve estar no módulo Mapa. Se for exposto, o bug pode estar em qualquer lugar do programa. Para mim, este é o principal benefício.
Doval

4

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.


1

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 limite da rede entre os processos do cliente e do servidor
  • O limite da API entre um aplicativo e uma biblioteca de terceiros
  • Limite de código interno entre diferentes domínios de negócios dentro do programa

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:

  • Os componentes externos podem ser trocados desde que implementem a especificação
  • Ponto natural para testes de unidade para validar o comportamento correto
  • Os testes de integração se tornam importantes - a especificação era ambígua?
  • Como desenvolvedor, você tem um mundo menor de preocupação ao trabalhar em uma tarefa específica

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.