Por que um programador gostaria de separar a implementação da interface?

18

O padrão de design da ponte separa a implementação da interface de um programa.

Por que isso é vantajoso?

David Faux
fonte
Para evitar uma abstração com vazamento
SK-logic
1
Isso já parece ser compreendido por todos, mas para novos espectadores, o Bridge Design não se refere à implementação e interface "de um programa", mas a partes, talvez até pequenas partes de um programa. Um programa inteiro será uma coleção de interfaces e implementações (com cada interface tendo uma ou mais implementações). A primeira frase talvez deva ser: "O padrão de design da ponte separa as interfaces de suas implementações em todo o código-fonte".
RalphChapin

Respostas:

35

Permite alterar a implementação independentemente da interface. Isso ajuda a lidar com requisitos variáveis.

O exemplo clássico é substituir a implementação de armazenamento em uma interface por algo maior, melhor, mais rápido, menor ou diferente sem precisar alterar o restante do sistema.

Daniel Pittman
fonte
23

Além da resposta de Daniel, separar interface da implementação por meio de conceitos como polimorfismo permite criar várias implementações da mesma interface que fazem coisas semelhantes de maneiras diferentes.

Por exemplo, muitos idiomas têm o conceito de fluxo em algum lugar da biblioteca padrão. Um fluxo é algo que armazena dados para acesso serial. Ele possui duas operações básicas: Ler (carregar o próximo número X de bytes do fluxo) e Escrever (adicionar um número X de bytes de dados ao fluxo) e, às vezes, um terceiro, Buscar (redefinir a "posição atual" do fluxo) para um novo local).

Esse é um conceito bastante simples, mas pense em todas as coisas que você poderia fazer com ele. O mais óbvio é interagir com os arquivos em disco. Um fluxo de arquivos permite ler dados de um arquivo ou gravá-lo. Mas e se você quisesse enviar dados por uma conexão de rede?

Se você dependesse das implementações diretamente, teria que escrever duas rotinas completamente diferentes para salvar os mesmos dados em um arquivo ou enviá-los pela rede. Mas se você possui uma interface de fluxo, é possível criar duas implementações diferentes ( FileStreame NetworkStream) que encapsulam os detalhes específicos do envio dos dados para onde eles precisam ir e, em seguida, você só precisa escrever o código que trata de salvar um arquivo uma vez . De repente, suas rotinas SaveToFilee SendOverNetworksão muito mais simples: elas apenas configuram um fluxo do tipo apropriado e o passam para a SaveDatarotina, que aceita uma interface de fluxo - ele não precisa se importar com o tipo, desde que possa executar o Operação de gravação - e salva os dados no fluxo.

Isso também significa que, se o formato dos dados for alterado, você não precisará alterá-lo em vários locais diferentes. Se você centralizar seu código de economia de dados em uma rotina que recebe um fluxo, esse é o único local em que ele precisa ser atualizado; portanto, você não pode introduzir acidentalmente um bug alterando apenas um local quando precisar alterar os dois. Portanto, separar as interfaces das implementações e usar o polimorfismo torna o código mais simples de ler e entender, e é menos provável que haja bugs.

Mason Wheeler
fonte
1
Esse é um dos recursos mais fortes do OOP. Eu adoro isso ...
Radu Murzea
1
Como esse formulário é diferente usando interfaces simples?
vainolo
@Vainolo: Ajuda na reutilização de código. Mesmo se você tiver vários tipos de fluxos, todos farão as mesmas coisas. Se você começar com uma IStreaminterface, precisará reinventar todo o conjunto de funcionalidades para cada fluxo. Mas se você começar com uma classe de fluxo abstrato base, ela poderá conter todo o estado e funcionalidade comuns e permitir que os descendentes implementem os recursos distintos.
Mason Wheeler
2
@RaduMurzea isso não é específico para OOP. As classes de tipo permitem que você faça a mesma coisa de uma maneira completamente fora do padrão.
Wes
@ Wes exatamente, só queria dizer o mesmo, e então eu li o seu comentário.
jhegedus
4

Você realmente tem duas perguntas muito diferentes aqui, embora elas estejam relacionadas.

A questão mais geral é a do título, por que você separaria a interface da implementação em geral. A segunda pergunta é por que o padrão de ponte é útil. Eles estão relacionados porque o padrão de ponte é uma maneira específica de separar a interface da implementação, que também tem outras consequências específicas.

A questão geral é algo vital para todo programador entender. É o que impede que as mudanças em um programa se propagem por toda parte. Não consigo imaginar seres humanos capazes de programar sem usar isso.

Quando você escreve uma instrução de adição simples em uma linguagem de programação, isso já é uma abstração (mesmo que não esteja usando sobrecarga de operador para adicionar matrizes ou algo parecido) que passa por muitos outros códigos antes de finalmente ser executado em um circuito no seu computador. Se não houvesse separação da interface (digamos, "3 + 5") da implementação (um monte de código de máquina), seria necessário alterar seu código sempre que a implementação fosse alterada (como você deseja executar em um novo processador).

Mesmo em um aplicativo CRUD simples, toda assinatura de método é, em um sentido amplo, a interface para sua implementação.

Todos esses tipos de abstração têm o mesmo objetivo básico - fazer com que o código de chamada expresse sua intenção da maneira mais abstrata possível, fornecendo ao implementador as informações necessárias. Isso fornece o mínimo possível de acoplamento entre eles e limita o efeito cascata quando o código precisa ser alterado o máximo possível.

Parece simples, mas na prática fica complicado.

O padrão de ponte é uma maneira específica de separar certos bits de implementação em interfaces. O diagrama de classes do padrão é mais informativo que a descrição. É mais uma maneira de ter módulos conectáveis ​​do que uma ponte, mas eles deram o nome de ponte porque é frequentemente usada onde os módulos foram criados antes da interface. Portanto, a criação de uma interface comum para implementações existentes semelhantes, classifica a diferença e permite que o código funcione com qualquer uma das implementações.

Então, digamos que você queira gravar um complemento em um processador de texto, mas que ele funcione em vários processadores de texto. Você pode criar uma interface que abstraia a funcionalidade baseada em processador de texto necessária (e que deve ser implementável por cada processador de texto, pois você não pode alterá-las) e um implementador dessa interface para cada processador de texto que você deseja oferecer suporte. Em seguida, seu aplicativo pode chamar essa interface e não se preocupar com os detalhes de cada processador de texto.

Na verdade, é um pouco mais detalhado do que isso, porque cada classe pode realmente ser uma hierarquia de classes (portanto, pode haver não apenas um processador de texto abstrato, mas um Documento abstrato, TextSelection abstrato, etc., com implementações concretas para cada um), mas é a mesma ideia.

É um pouco como uma fachada, exceto que, neste caso, a camada de abstração está focada em fornecer a mesma interface para vários sistemas subjacentes.

Está relacionado à Inversão de controle, já que o implementador concreto será passado para métodos ou construtores e determinará a implementação real chamada.

psr
fonte