Quando você usa o padrão de ponte? Como é diferente do padrão do adaptador?


154

Alguém já usou o Bridge Pattern em um aplicativo do mundo real? Se sim, como você o usou? Sou eu, ou é apenas o Padrão do Adaptador com uma pequena injeção de dependência jogada na mistura? Ele realmente merece seu próprio padrão?


Por favor, considere aceitar uma resposta diferente para esta pergunta. A resposta atualmente aceita é incorreta e inútil. Respostas mais recentes são muito superiores.
jaco0646 4/02

O livro do GoF responde diretamente a essa pergunta.
jaco0646 25/03

Respostas:


76

Um exemplo clássico do padrão Bridge é usado na definição de formas em um ambiente de interface do usuário (consulte a entrada Wikipedia do padrão Bridge ). O padrão Bridge é um composto dos padrões Template e Strategy .

É uma visão comum alguns aspectos do padrão do adaptador no padrão Bridge. No entanto, para citar este artigo :

À primeira vista, o padrão Bridge se parece muito com o padrão Adapter, pois uma classe é usada para converter um tipo de interface em outro. No entanto, a intenção do padrão do adaptador é fazer com que as interfaces de uma ou mais classes sejam iguais às de uma classe específica. O padrão Bridge foi projetado para separar a interface de uma classe de sua implementação, para que você possa variar ou substituir a implementação sem alterar o código do cliente.


1
Bridge não tem nada a ver com modelo ou estratégia. Bridge é um padrão estrutural. Modelo e Estratégia são padrões comportamentais.
jaco0646 4/02

249

Há uma combinação das respostas de Federico e John .

Quando:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

Refatorar para:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red

6
Por que você faria herança por cores?
vainolo

10
@vainolo porque a cor é uma interface e azul, vermelho são as cores concretas
Weltschmerz

3
Isso é apenas uma refatoração. Intenção do padrão de ponte: "Desacoplar uma abstração de sua implementação para que os dois possam variar independentemente". Onde está a abstração e onde está a implementação aqui?
clapas

1
O Retângulo (Cor) não é mais abstrato que o BlueRectangle?
Anton Shchastnyi

2
@clapas, Abstraction é "Shape.color" da propriedade, portanto, a classe Red e Blue são implementadas e a interface Color é bridge.
reco

230

O padrão Bridge é uma aplicação do antigo conselho, "prefira composição sobre herança". Torna-se útil quando você deve subclassificar momentos diferentes de maneiras ortogonais entre si. Digamos que você deve implementar uma hierarquia de formas coloridas. Você não subclassaria Shape com retângulo e círculo e, em seguida, subclasse Rectangle com RedRectangle, BlueRectangle e GreenRectangle e o mesmo para Circle, não é? Você prefere dizer que cada forma tem uma cor e implementar uma hierarquia de cores, e esse é o padrão de ponte. Bem, eu não implementaria uma "hierarquia de cores", mas você entendeu ...


1
Veja também o diagrama de Anton Shchastnyi abaixo para uma ilustração gráfica desta explicação.
NoRetNumerique

2
Eu não acho que uma cor seja um bom exemplo para uma hierarquia de implementação, é bastante confusa. Não é um bom exemplo do padrão Bridge, em "padrões de design" pelo GoF, onde a implementação é dependente de plataforma: PM da IBM, do UNIX X etc.
Clapas

215

Quando:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

Refatorar para:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

3
Eu acho que é abordagem muito pragmática para os padrões: 1) descrever suboptimal direto projeto 2) refactor projeto / código para melhor consignado um
Alexey

1
Use o conceito de matemática para explicar o padrão de design do Bridge. Muito interessado.
Jian Huang

1
Isso é apenas uma refatoração. Intenção do padrão de ponte: "Desacoplar uma abstração de sua implementação para que os dois possam variar independentemente". Onde está a abstração e onde está a implementação aqui?
clapas

John coloca bem em uma postagem no blog . Foi uma boa leitura para uma visão geral de alto nível.
Vaibhav Bhalla

29

Adapter e Bridge certamente estão relacionados, e a distinção é sutil. É provável que algumas pessoas que pensam estar usando um desses padrões estejam realmente usando o outro padrão.

A explicação que eu vi é que o Adapter é usado quando você está tentando unificar as interfaces de algumas classes incompatíveis que já existem . O adaptador funciona como um tipo de tradutor para implementações que podem ser consideradas herdadas .

Enquanto o padrão Bridge é usado para código com maior probabilidade de ser greenfield. Você está projetando o Bridge para fornecer uma interface abstrata para uma implementação que precisa variar, mas também define a interface dessas classes de implementação.

Drivers de dispositivo é um exemplo frequentemente citado de Bridge, mas eu diria que é um Bridge se você estiver definindo as especificações da interface para fornecedores de dispositivos, mas é um Adaptador se estiver usando drivers de dispositivo existentes e criando uma classe de wrapper para fornecer uma interface unificada.

Portanto, em termos de código, os dois padrões são muito semelhantes. Em termos de negócios, eles são diferentes.

Consulte também http://c2.com/cgi/wiki?BridgePattern


Hey Bill. Não entendo por que necessariamente usamos o padrão Bridge nos drivers de dispositivo. Quero dizer, podemos delegar facilmente a implementação (de leitura, gravação, busca etc.) para a classe correta via polimorfismo, certo? Ou talvez com um visitante? Por que tem que ser Bridge? Desde já, obrigado.
Stdout

1
@ zgulser, sim, você usa polimorfismo. O padrão Bridge descreve um tipo de uso de subclasses para separar a implementação da abstração.
precisa

Você queria dissociar a implementação do Shape (ou seja, retângulo) do dia seguinte Abstração de cores, certo? E acredito que você está dizendo que há várias maneiras de fazer isso e o Bridge é apenas uma delas.
Stdout

Sim, a subclasse tem outros usos. Essa maneira particular de usar subclasses é o que o torna o padrão Bridge.
Bill Karwin

E a dissociação, quero dizer, é da interface abstrata Shape para uma implementação concreta do retângulo. Portanto, você pode escrever um código que precise de um objeto do tipo "Shape", mesmo que o objeto concreto seja realmente uma subclasse de Shape.
Bill Karwin

27

Na minha experiência, o Bridge é um padrão frequentemente recorrente, porque é a solução sempre que há duas dimensões ortogonais no domínio . Por exemplo, formas e métodos de desenho, comportamentos e plataformas, formatos de arquivo e serializadores e assim por diante.

E um conselho: sempre pense nos padrões de design da perspectiva conceitual , não da perspectiva da implementação. Do ponto de vista correto, o Bridge não pode ser confundido com o Adapter, porque resolve um problema diferente, e a composição é superior à herança, não por causa de si mesma, mas porque permite lidar com preocupações ortogonais separadamente.


22

A intenção do Bridge e do Adapter é diferente e precisamos dos dois padrões separadamente.

Padrão de ponte:

  1. É um padrão estrutural
  2. Abstração e implementação não são vinculadas no tempo de compilação
  3. Abstração e implementação - ambas podem variar sem impacto no cliente
  4. Usa composição sobre herança.

Use o padrão Bridge quando:

  1. Você deseja a ligação em tempo de execução da implementação,
  2. Você tem uma proliferação de classes resultante de uma interface acoplada e várias implementações,
  3. Você deseja compartilhar uma implementação entre vários objetos,
  4. Você precisa mapear hierarquias de classe ortogonais.

A resposta de John Sonmez mostra claramente a eficácia do padrão de ponte na redução da hierarquia de classes.

Você pode consultar o link da documentação abaixo para obter uma melhor visão do padrão de ponte com o exemplo de código

Padrão do adaptador :

  1. Ele permite que duas interfaces não relacionadas trabalhem juntas através de diferentes objetos, possivelmente desempenhando o mesmo papel.
  2. Modifica a interface original.

Principais diferenças:

  1. O adaptador faz as coisas funcionarem após serem projetadas; Bridge os faz trabalhar antes deles.
  2. O Bridge é projetado antecipadamente para permitir que a abstração e a implementação variem independentemente . O adaptador é adaptado para fazer com que classes não relacionadas funcionem juntas.
  3. A intenção: o adaptador permite que duas interfaces não relacionadas trabalhem juntas. O Bridge permite que a Abstração e a implementação variem independentemente.

Pergunta SE relacionada com diagrama UML e código de trabalho:

Diferença entre o padrão Bridge e o adaptador

Artigos úteis:

artigo de padrão de ponte de criação de fontes

artigo do padrão do adaptador de criação de fontes

artigo de padrão de ponte journaldev

EDITAR:

Exemplo do mundo real do padrão de ponte (conforme sugestão de meta.stackoverflow.com, exemplo de site de documentação incorporado neste post, pois a documentação vai se pôr)

O padrão de ponte desacopla a abstração da implementação para que ambos possam variar independentemente. Foi alcançado com a composição e não com a herança.

Padrão de ponte UML da Wikipedia:

Padrão de ponte UML da Wikipedia

Você tem quatro componentes neste padrão.

Abstraction: Define uma interface

RefinedAbstraction: Implementa abstração:

Implementor: Define uma interface para implementação

ConcreteImplementor: Implementa a interface do implementador.

The crux of Bridge pattern :Duas hierarquias de classe ortogonais usando composição (e sem herança). A hierarquia de abstração e a hierarquia de implementação podem variar independentemente. A implementação nunca se refere à abstração. Abstração contém a interface de Implementação como um membro (através da composição). Essa composição reduz mais um nível de hierarquia de herança.

Caso de uso de palavra real:

Permita que veículos diferentes tenham ambas as versões do sistema manual e automático.

Código de exemplo:

/* Implementor interface*/
interface Gear{
    void handleGear();
}

/* Concrete Implementor - 1 */
class ManualGear implements Gear{
    public void handleGear(){
        System.out.println("Manual gear");
    }
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
    public void handleGear(){
        System.out.println("Auto gear");
    }
}
/* Abstraction (abstract class) */
abstract class Vehicle {
    Gear gear;
    public Vehicle(Gear gear){
        this.gear = gear;
    }
    abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
    public Car(Gear gear){
        super(gear);
        // initialize various other Car components to make the car
    }
    public void addGear(){
        System.out.print("Car handles ");
        gear.handleGear();
    }
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
    public Truck(Gear gear){
        super(gear);
        // initialize various other Truck components to make the car
    }
    public void addGear(){
        System.out.print("Truck handles " );
        gear.handleGear();
    }
}
/* Client program */
public class BridgeDemo {    
    public static void main(String args[]){
        Gear gear = new ManualGear();
        Vehicle vehicle = new Car(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Car(gear);
        vehicle.addGear();

        gear = new ManualGear();
        vehicle = new Truck(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Truck(gear);
        vehicle.addGear();
    }
}

resultado:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

Explicação:

  1. Vehicle é uma abstração.
  2. Care Trucksão duas implementações concretas de Vehicle.
  3. Vehicledefine um método resumo: addGear().
  4. Gear é interface do implementador
  5. ManualGeare AutoGearsão duas implementações de Gear
  6. Vehiclecontém implementorinterface em vez de implementar a interface. CompositonA interface do implementador é o cerne desse padrão: permite que a abstração e a implementação variem independentemente.
  7. Care Truckdefinir a implementação (abstração redefinido) de abstração: addGear(): Contém Gear- De qualquer ManualouAuto

Casos de uso para o padrão Bridge :

  1. Abstração e Implementação podem mudar independentes entre si e não são vinculadas no tempo de compilação
  2. Mapeie hierarquias ortogonais - uma para Abstração e outra para Implementação .

"O adaptador faz as coisas funcionarem depois de projetadas; o Bridge as faz funcionar antes de serem". Você pode procurar no adaptador conectável. É uma variação do Adapter descrita pelo GoF na seção "Adapter" do livro Design Patterns. O objetivo é criar uma interface para classes que ainda não existem. Um adaptador conectável não é um Bridge, então não acho que o primeiro ponto seja válido.
C1moore 23/11

Apesar de engrenagem manual e automática pode exigir implementação diferente para o caminhão e carro
andigor

9

Eu usei o padrão de ponte no trabalho. Eu programa em C ++, onde geralmente é chamado de idioma PIMPL (ponteiro para implementação). Se parece com isso:

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  

Neste exemplo class Acontém a interface e class Aimpla implementação.

Um uso para esse padrão é expor apenas alguns dos membros públicos da classe de implementação, mas não outros. No exemplo, apenas Aimpl::foo()pode ser chamado através da interface pública de A, mas nãoAimpl::bar()

Outra vantagem é que você pode definir Aimplem um arquivo de cabeçalho separado que não precise ser incluído pelos usuários do A. Tudo o que você precisa fazer é usar uma declaração de encaminhamento de Aimplantes de Aser definida e mover as definições de todas as funções de membro referenciadas pImplno arquivo .cpp. Isso permite manter o Aimplcabeçalho privado e reduzir o tempo de compilação.


2
Se você usar esse padrão, o AImpl nem precisará de um cabeçalho. Eu só colocá-lo em linha no arquivo de implementação para a classe A
1800 INFORMATION

Seu implementador é privado. Eu tenho uma nova pergunta em relação a isso, consulte stackoverflow.com/questions/17680762/…
Roland

7

Para colocar exemplo de forma no código:

#include<iostream>
#include<string>
#include<cstdlib>

using namespace std;

class IColor
{
public:
    virtual string Color() = 0;
};

class RedColor: public IColor
{
public:
    string Color()
    {
        return "of Red Color";
    }
};

class BlueColor: public IColor
{
public:
    string Color()
    {
        return "of Blue Color";
    }
};


class IShape
{
public:
virtual string Draw() = 0;
};

class Circle: public IShape
{
        IColor* impl;
    public:
        Circle(IColor *obj):impl(obj){}
        string Draw()
        {
            return "Drawn a Circle "+ impl->Color();
        }
};

class Square: public IShape
{
        IColor* impl;
    public:
        Square(IColor *obj):impl(obj){}
        string Draw()
        {
        return "Drawn a Square "+ impl->Color();;
        }
};

int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();

IShape* sq = new Square(red);
IShape* cr = new Circle(blue);

cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();

delete red;
delete blue;
return 1;
}

A saída é:

Drawn a Square of Red Color
Drawn a Circle of Blue Color

Observe a facilidade com que novas cores e formas podem ser adicionadas ao sistema sem levar a uma explosão de subclasses devido a permutações.


0

para mim, eu penso nisso como um mecanismo onde você pode trocar interfaces. No mundo real, você pode ter uma classe que pode usar mais de uma interface. O Bridge permite que você troque.


0

Você está trabalhando para uma companhia de seguros onde desenvolve um aplicativo de fluxo de trabalho que gerencia diferentes tipos de tarefas: contabilidade, contrato, reclamações. Esta é a abstração. No lado da implementação, você deve poder criar tarefas de diferentes fontes: email, fax, e-messaging.

Você inicia seu design com estas classes:

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Agora, como cada fonte deve ser tratada de uma maneira específica, você decide especializar cada tipo de tarefa:

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

Você acaba com 13 aulas. Adicionar um tipo de tarefa ou tipo de fonte se torna desafiador. Usar o padrão de ponte produz algo mais fácil de manter, dissociando a tarefa (a abstração) da fonte (que é uma preocupação de implementação):

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Adicionar um tipo de tarefa ou fonte agora é muito mais fácil.

Nota: A maioria dos desenvolvedores não criaria a hierarquia de 13 classes antecipadamente para lidar com esse problema. No entanto, na vida real, talvez você não conheça com antecedência o número de tipos de fonte e tarefa; se você tiver apenas uma fonte e dois tipos de tarefas, provavelmente não desacoplaria a Tarefa da Origem. Em seguida, a complexidade geral aumenta à medida que novas fontes e tipos de tarefas são adicionados. Em algum momento, você irá refatorar e, na maioria das vezes, acabar com uma solução semelhante a uma ponte.


-5
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}

5
Fiz uma votação baixa porque sinto que esta é uma resposta complicada e mal formatada.
Zimano

1
Totalmente de acordo, como você pode postar respostas neste site sem uma atenção mínima para recuo de código e clareza
Massimiliano Kraus
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.