Padrões de Projetos - Bridge
Afluentes: Modelos, métodos e técnicas da engenharia de software
Definição
O padrão de projeto Bridge (Ponte) é um padrão estrutural que tem como objetivo desacoplar uma abstração da sua implementação, permitindo que ambas possam variar independentemente. Ele é útil quando você tem uma hierarquia de classes e quer evitar que as variações nas abstrações e nas implementações levem a uma explosão de subclasses.
A ideia principal é usar a composição em vez de herança para ligar as duas partes: uma classe abstrata (a abstração) mantém uma referência a uma interface ou classe que será responsável pela implementação.
Estrutura do padrão Bridge
- Abstração: Define a interface para o controle das operações e mantém uma referência para o objeto de implementação.
- Implementação (Implementor): Interface que define as operações que serão implementadas concretamente.
- Abstração Refinada: Uma subclasse de abstração que adiciona mais funcionalidades, mas ainda mantém a referência à implementação.
- Implementação Concreta: Implementa a interface de implementação, oferecendo comportamentos específicos.
Exemplo Simples em Java
Vamos supor que estamos desenvolvendo uma aplicação para diferentes dispositivos de controle remoto que operam TVs e rádios. O padrão Bridge nos ajuda a separar a abstração (o controle remoto) da implementação (o dispositivo controlado).
Dispositivo.java
A interface Dispositivo
é a implementação que será usada pelas classes concretas TV
e Radio
.
// Implementor (interface de implementação)
interface Dispositivo {
void ligar();
void desligar();
void ajustarVolume(int nivel);
}
TV.java
// Implementação concreta do dispositivo TV
class TV implements Dispositivo {
private int volume;
@Override
public void ligar() {
System.out.println("TV ligada");
}
@Override
public void desligar() {
System.out.println("TV desligada");
}
@Override
public void ajustarVolume(int nivel) {
this.volume = nivel;
System.out.println("Volume da TV ajustado para: " + this.volume);
}
}
Radio.java
// Implementação concreta do dispositivo Rádio
class Radio implements Dispositivo {
private int volume;
@Override
public void ligar() {
System.out.println("Rádio ligado");
}
@Override
public void desligar() {
System.out.println("Rádio desligado");
}
@Override
public void ajustarVolume(int nivel) {
this.volume = nivel;
System.out.println("Volume do rádio ajustado para: " + this.volume);
}
}
ControleRemoto.java
A classe abstrata ControleRemoto
define a interface de controle, com métodos ligar()
, desligar()
e ajustarVolume(int nivel)
.
// Abstração do Controle Remoto
abstract class ControleRemoto {
protected Dispositivo dispositivo;
public ControleRemoto(Dispositivo dispositivo) {
this.dispositivo = dispositivo;
}
public abstract void ligar();
public abstract void desligar();
public abstract void ajustarVolume(int nivel);
}
ControleSimples.java
As classes ControleSimples
e ControleAvancado
são abstrações refinadas que podem adicionar funcionalidades extras, mas ambas trabalham com qualquer tipo de dispositivo (TV
ou Radio
), demonstrando o desacoplamento entre a abstração e a implementação.
// Abstração refinada do controle remoto simples
class ControleSimples extends ControleRemoto {
public ControleSimples(Dispositivo dispositivo) {
super(dispositivo);
}
@Override
public void ligar() {
dispositivo.ligar();
}
@Override
public void desligar() {
dispositivo.desligar();
}
@Override
public void ajustarVolume(int nivel) {
dispositivo.ajustarVolume(nivel);
}
}
ControleAvancado.java
// Abstração refinada do controle remoto avançado
class ControleAvancado extends ControleRemoto {
public ControleAvancado(Dispositivo dispositivo) {
super(dispositivo);
}
public void ligar() {
System.out.println("Iniciando controle avançado...");
dispositivo.ligar();
}
public void desligar() {
System.out.println("Desligando com controle avançado...");
dispositivo.desligar();
}
public void ajustarVolume(int nivel) {
System.out.println("Controle avançado ajustando volume...");
dispositivo.ajustarVolume(nivel);
}
}
Classe principal do programa Main.java
Veja que isso nos permite adicionar novos tipos de dispositivos ou novos tipos de controles remotos sem modificar as classes existentes.
public class Main {
public static void main(String[] args) {
// Utilizando a abstração com uma TV
Dispositivo tv = new TV();
ControleRemoto controleSimplesTV = new ControleSimples(tv);
controleSimplesTV.ligar();
controleSimplesTV.ajustarVolume(10);
controleSimplesTV.desligar();
// Utilizando a abstração com um rádio
Dispositivo radio = new Radio();
ControleRemoto controleAvancadoRadio = new ControleAvancado(radio);
controleAvancadoRadio.ligar();
controleAvancadoRadio.ajustarVolume(5);
controleAvancadoRadio.desligar();
}
}
Vantagens e Desvantagens
O padrão Bridge oferece várias vantagens e desvantagens que precisam ser consideradas ao decidir utilizá-lo em um projeto de software. Aqui estão as principais:
Vantagens do Padrão Bridge
- Desacoplamento entre abstração e implementação:
- O principal benefício do Bridge é separar a abstração da implementação, permitindo que ambos possam evoluir de forma independente. Isso ajuda a evitar a criação de muitas subclasses (evitando a "explosão de classes"), já que as variações são tratadas de maneira modular.
- Maior flexibilidade:
- Permite variar tanto a abstração quanto a implementação sem precisar alterar a estrutura principal do código. Isso é útil em sistemas que precisam suportar várias plataformas ou dispositivos.
- Facilidade de manutenção:
- Como a abstração e a implementação são desacopladas, a manutenção se torna mais simples, pois alterações em uma parte do código não afetam diretamente a outra. Isso reduz o impacto de mudanças.
- Facilita extensões futuras:
- Adicionar novas implementações ou abstrações se torna mais simples e sem a necessidade de reescrever o código existente. Isso é especialmente útil em projetos que requerem expansões e atualizações frequentes.
- Reduz a duplicação de código:
- Como o padrão Bridge evita que funcionalidades relacionadas à abstração e à implementação fiquem sobrepostas, há uma redução significativa na duplicação de código em comparações com padrões como herança.
Desvantagens do Padrão Bridge
- Complexidade inicial:
- Implementar o padrão Bridge pode adicionar uma complexidade inicial ao projeto, pois envolve mais classes e interfaces, mesmo que a aplicação seja simples. Isso pode ser visto como um "overhead" em termos de estrutura.
- Sobrecarga de abstrações:
- Em sistemas muito pequenos ou simples, o uso do Bridge pode ser desnecessário e criar um nível de abstração adicional que não traz muitos benefícios, tornando o código mais difícil de seguir e depurar.
- Maior número de classes:
- O padrão exige a criação de mais classes e interfaces, o que pode tornar a navegação e a leitura do código um pouco mais complicada. Isso é uma desvantagem se o projeto não for grande o suficiente para se beneficiar dessa flexibilidade.
- Requer bom planejamento:
- O padrão Bridge é mais vantajoso quando existe uma previsão clara de que tanto a abstração quanto a implementação vão evoluir e variar ao longo do tempo. Se esse cenário não for realista, o esforço adicional pode ser desperdiçado.
- Dificuldade em casos muito simples:
- Para casos onde há poucas variantes de abstração ou implementação, o Bridge pode ser desnecessário e apenas complicar a solução. Nesse caso, padrões mais simples, como herança direta ou uma fábrica (Factory), poderiam ser suficientes.
Quando Usar o Padrão Bridge?
O Bridge é mais indicado quando:
- Você sabe que haverá múltiplas variações tanto na abstração quanto na implementação.
- Você deseja manter o código flexível para futuras expansões e manutenções.
- O sistema requer mudanças frequentes ou integrações com diferentes plataformas/dispositivos.
Por outro lado, se o seu sistema é simples e não há variações significativas, o Bridge pode ser desnecessário e adicionar complexidade sem um benefício claro.