Padrão de ponte - Bridge pattern

O padrão de ponte é um padrão de design usado em engenharia de software que visa "separar uma abstração de sua implementação para que as duas possam variar independentemente" , introduzido pela Gangue dos Quatro . A ponte usa encapsulamento , agregação e pode usar herança para separar responsabilidades em classes diferentes .

Quando uma classe varia, muitas vezes, as características de programação orientada a objetos tornam-se muito útil porque as mudanças a um programa de código pode ser feito facilmente com um mínimo de conhecimentos sobre o programa. O padrão de ponte é útil quando a classe e o que ela faz variam com frequência. A própria classe pode ser considerada uma abstração e o que a classe pode fazer como implementação . O padrão de ponte também pode ser considerado como duas camadas de abstração.

Quando há apenas uma implementação fixa, esse padrão é conhecido como idioma Pimpl no mundo C ++ .

O padrão de ponte é frequentemente confundido com o padrão do adaptador e geralmente é implementado usando o padrão do adaptador de objeto ; por exemplo, no código Java abaixo.

Variante: a implementação pode ser desacoplada ainda mais, adiando a presença da implementação até o ponto em que a abstração é utilizada.

Visão geral

O padrão de design Bridge é um dos vinte e três padrões de design GoF bem conhecidos que descrevem como resolver problemas de design recorrentes para projetar software orientado a objeto flexível e reutilizável, ou seja, objetos que são mais fáceis de implementar, alterar, testar e reuso.

Que problemas o padrão de projeto do Bridge pode resolver?

  • Uma abstração e sua implementação devem ser definidas e estendidas independentemente uma da outra.
  • Uma vinculação de tempo de compilação entre uma abstração e sua implementação deve ser evitada para que uma implementação possa ser selecionada em tempo de execução.

Ao usar subclasses, diferentes subclasses implementam uma classe abstrata de maneiras diferentes. Mas uma implementação está ligada à abstração em tempo de compilação e não pode ser alterada em tempo de execução.

Que solução o padrão de design do Bridge descreve?

  • Separe uma abstração ( Abstraction ) de sua implementação ( Implementor ) colocando-as em hierarquias de classes separadas.
  • Implemente o Abstraction em termos de (delegando a) um Implementor objeto.

Isso permite configurar um Abstraction com um Implementor objeto em tempo de execução.
Consulte também a classe Unified Modeling Language e o diagrama de sequência abaixo.

Estrutura

Classe UML e diagrama de sequência

Um exemplo de classe UML e diagrama de sequência para o padrão de design Bridge.

No diagrama de classes da Unified Modeling Language acima , uma abstraction ( Abstraction ) não é implementada como de costume em uma única hierarquia de herança. Em vez disso, há uma hierarquia para uma abstração ( Abstraction ) e uma hierarquia separada para sua implementação ( Implementor ), o que torna as duas independentes uma da outra. A Abstraction interface ( operation() ) é implementada em termos de (delegando a) a Implementor interface ( imp.operationImp() ).
O diagrama de seqüência UML mostra as interações em tempo de execução: O Abstraction1 objeto delega a implementação ao Implementor1 objeto (chamando operationImp() em Implementor1 ), que executa a operação e retorna para Abstraction1 .

Diagrama de classes

Bridge UML class diagram.svg

Abstração (classe abstrata)
define a interface abstrata
mantém a referência do implementador.
RefinedAbstraction (classe normal)
estende a interface definida por Abstraction
Implementador (interface)
define a interface para classes de implementação
ConcreteImplementor (classe normal)
implementa a interface do Implementador
Ponte em LePUS3 ( legenda )

Exemplo

C #

O padrão de ponte compõe objetos na estrutura de árvore. Ele separa a abstração da implementação. Aqui, a abstração representa o cliente a partir do qual os objetos serão chamados. Um exemplo implementado em C # é fornecido abaixo

// Helps in providing truly decoupled architecture
public interface IBridge
{
    void Function1();
    void Function2();
}

public class Bridge1 : IBridge
{
    public void Function1()
    {
        Console.WriteLine("Bridge1.Function1");
    }

    public void Function2()
    {
        Console.WriteLine("Bridge1.Function2");
    }
}

public class Bridge2 : IBridge
{
    public void Function1()
    {
        Console.WriteLine("Bridge2.Function1");
    }

    public void Function2()
    {
        Console.WriteLine("Bridge2.Function2");
    }
}

public interface IAbstractBridge
{
    void CallMethod1();
    void CallMethod2();
}

public class AbstractBridge : IAbstractBridge
{
    public IBridge bridge;

    public AbstractBridge(IBridge bridge)
    {
        this.bridge = bridge;
    }

    public void CallMethod1()
    {
        this.bridge.Function1();
    }

    public void CallMethod2()
    {
        this.bridge.Function2();
    }
}

As classes Bridge são a implementação que usa a mesma arquitetura orientada a interface para criar objetos. Por outro lado, a abstração pega uma instância da classe de implementação e executa seu método. Assim, eles estão completamente separados um do outro.

Cristal

abstract class DrawingAPI
  abstract def draw_circle(x : Float64, y : Float64, radius : Float64)
end

class DrawingAPI1 < DrawingAPI
  def draw_circle(x : Float, y : Float, radius : Float)
    "API1.circle at #{x}:#{y} - radius: #{radius}"
  end
end

class DrawingAPI2 < DrawingAPI
  def draw_circle(x : Float64, y : Float64, radius : Float64)
    "API2.circle at #{x}:#{y} - radius: #{radius}"
  end
end

abstract class Shape
  protected getter drawing_api : DrawingAPI

  def initialize(@drawing_api)
  end

  abstract def draw
  abstract def resize_by_percentage(percent : Float64)
end

class CircleShape < Shape
  getter x : Float64
  getter y : Float64
  getter radius : Float64

  def initialize(@x, @y, @radius, drawing_api : DrawingAPI)
    super(drawing_api)
  end

  def draw
    @drawing_api.draw_circle(@x, @y, @radius)
  end

  def resize_by_percentage(percent : Float64)
    @radius *= (1 + percent/100)
  end
end

class BridgePattern
  def self.test
    shapes = [] of Shape
    shapes << CircleShape.new(1.0, 2.0, 3.0, DrawingAPI1.new)
    shapes << CircleShape.new(5.0, 7.0, 11.0, DrawingAPI2.new)

    shapes.each do |shape|
      shape.resize_by_percentage(2.5)
      puts shape.draw
    end
  end
end

BridgePattern.test

Resultado

API1.circle at 1.0:2.0 - radius: 3.075
API2.circle at 5.0:7.0 - radius: 11.275

C ++

#include <iostream>
#include <string>
#include <vector>


class DrawingAPI {
  public:
    virtual ~DrawingAPI() = default;
    virtual std::string DrawCircle(float x, float y, float radius) const = 0;
};

class DrawingAPI01 : public DrawingAPI {
  public:
    std::string DrawCircle(float x, float y, float radius) const override {
      return "API01.circle at " + std::to_string(x) + ":" + std::to_string(y) +
        " - radius: " + std::to_string(radius); 
    }
};

class DrawingAPI02 : public DrawingAPI {
  public:
    std::string DrawCircle(float x, float y, float radius) const override {
      return "API02.circle at " + std::to_string(x) + ":" + std::to_string(y) +
        " - radius: " + std::to_string(radius); 
    }
};

class Shape {
  public:
    Shape(const DrawingAPI& drawing_api) : drawing_api_(drawing_api) {}
    virtual ~Shape() = default;

    virtual std::string Draw() const = 0;
    virtual float ResizeByPercentage(const float percent) = 0;

  protected:
    const DrawingAPI& drawing_api_;
};

class CircleShape: public Shape {
  public:    
    CircleShape(float x, float y, float radius, const DrawingAPI& drawing_api)
      : Shape(drawing_api), x_(x), y_(y), radius_(radius) {}

    std::string Draw() const override {
        return drawing_api_.DrawCircle(x_, y_, radius_);
    }

    float ResizeByPercentage(const float percent) override {
      return radius_ *= (1.0f + percent/100.0f);
    }
  
  private:
    float x_, y_, radius_;
};

int main(int argc, char** argv) {
  std::vector<CircleShape> shapes {
    CircleShape{1.0f, 2.0f, 3.0f, DrawingAPI01{}},
    CircleShape{5.0f, 7.0f, 11.0f, DrawingAPI02{}}
  }; 

  for (auto& shape: shapes) {
    shape.ResizeByPercentage(2.5);
    std::cout << shape.Draw() << std::endl;
  }

  return 0;
}

Resultado:

API01.circle at 1.000000:2.000000 - radius: 3.075000
API02.circle at 5.000000:7.000000 - radius: 11.275000

Java

O programa Java a seguir define uma conta bancária que separa as operações da conta do registro dessas operações.

// Logger has two implementations: info and warning
@FunctionalInterface
interface Logger {
    void log(String message);
    
    static Logger info() {
        return message -> System.out.println("info: " + message);
    }
    static Logger warning() {
        return message -> System.out.println("warning: " + message);
    }
}

abstract class AbstractAccount {
    private Logger logger = Logger.info();
    
    public void setLogger(Logger logger) {
        this.logger = logger;
    }
    
    // the logging part is delegated to the Logger implementation
    protected void operate(String message, boolean result) {
        logger.log(message + " result " + result);
    }
}

class SimpleAccount extends AbstractAccount {
    private int balance;
    
    public SimpleAccount(int balance) {
        this.balance = balance;
    }
    
    public boolean isBalanceLow() {
        return balance < 50;
    }
    
    public void withdraw(int amount) {
        boolean shouldPerform = balance >= amount;
        if (shouldPerform) {
            balance -= amount;
        }
        operate("withdraw " + amount, shouldPerform);
    }
}

public class BridgeDemo {
    public static void main(String[] args) {
        SimpleAccount account = new SimpleAccount(100);
        account.withdraw(75);
        
        if (account.isBalanceLow()) {
            // you can also change the Logger implementation at runtime
            account.setLogger(Logger.warning());
        }
        
        account.withdraw(10);
        account.withdraw(100);
    }
}

Irá produzir:

info: withdraw 75 result true
warning: withdraw 10 result true
warning: withdraw 100 result false

PHP

interface DrawingAPI
{
    function drawCircle($x, $y, $radius);
}

class DrawingAPI1 implements DrawingAPI
{
    public function drawCircle($x, $y, $radius)
    {
        echo "API1.circle at $x:$y radius $radius.\n";
    }
}

class DrawingAPI2 implements DrawingAPI
{
    public function drawCircle($x, $y, $radius)
    {
        echo "API2.circle at $x:$y radius $radius.\n";
    }
}

abstract class Shape
{
    protected $drawingAPI;

    public abstract function draw();
    public abstract function resizeByPercentage($pct);

    protected function __construct(DrawingAPI $drawingAPI)
    {
        $this->drawingAPI = $drawingAPI;
    }
}

class CircleShape extends Shape
{
    private $x;
    private $y;
    private $radius;

    public function __construct($x, $y, $radius, DrawingAPI $drawingAPI)
    {
        parent::__construct($drawingAPI);
        $this->x = $x;
        $this->y = $y;
        $this->radius = $radius;
    }

    public function draw()
    {
        $this->drawingAPI->drawCircle($this->x, $this->y, $this->radius);
    }

    public function resizeByPercentage($pct)
    {
        $this->radius *= $pct;
    }
}

class Tester
{
    public static function main()
    {
        $shapes = array(
            new CircleShape(1, 3, 7,  new DrawingAPI1()),
            new CircleShape(5, 7, 11, new DrawingAPI2()),
        );

        foreach ($shapes as $shape) {
            $shape->resizeByPercentage(2.5);
            $shape->draw();
        }
    }
}

Tester::main();

Resultado:

API1.circle at 1:3 radius 17.5
API2.circle at 5:7 radius 27.5

Scala

trait DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double)
}

class DrawingAPI1 extends DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double) = println(s"API #1 $x $y $radius")
}

class DrawingAPI2 extends DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double) = println(s"API #2 $x $y $radius")
}

abstract class Shape(drawingAPI: DrawingAPI) {
  def draw()
  def resizePercentage(pct: Double)
}

class CircleShape(x: Double, y: Double, var radius: Double, drawingAPI: DrawingAPI)
    extends Shape(drawingAPI: DrawingAPI) {

  def draw() = drawingAPI.drawCircle(x, y, radius)

  def resizePercentage(pct: Double) { radius *= pct }
}

object BridgePattern {
  def main(args: Array[String]) {
    Seq (
	new CircleShape(1, 3, 5, new DrawingAPI1),
	new CircleShape(4, 5, 6, new DrawingAPI2)
    ) foreach { x =>
        x.resizePercentage(3)
        x.draw()			
      }	
  }
}

Pitão

"""
Bridge pattern example.
"""
from abc import ABCMeta, abstractmethod


NOT_IMPLEMENTED = "You should implement this."


class DrawingAPI:
    __metaclass__ = ABCMeta

    @abstractmethod
    def draw_circle(self, x, y, radius):
        raise NotImplementedError(NOT_IMPLEMENTED)


class DrawingAPI1(DrawingAPI):
    def draw_circle(self, x, y, radius):
        return f"API1.circle at {x}:{y} - radius: {radius}"


class DrawingAPI2(DrawingAPI):
    def draw_circle(self, x, y, radius):
        return f"API2.circle at {x}:{y} - radius: {radius}"


class DrawingAPI3(DrawingAPI):
    def draw_circle(self, x, y, radius):
        return f"API3.circle at {x}:{y} - radius: {radius}"


class Shape:
    __metaclass__ = ABCMeta

    drawing_api = None
    def __init__(self, drawing_api):
        self.drawing_api = drawing_api

    @abstractmethod
    def draw(self):
        raise NotImplementedError(NOT_IMPLEMENTED)

    @abstractmethod
    def resize_by_percentage(self, percent):
        raise NotImplementedError(NOT_IMPLEMENTED)


class CircleShape(Shape):
    def __init__(self, x, y, radius, drawing_api):
        self.x = x
        self.y = y
        self.radius = radius
        super(CircleShape, self).__init__(drawing_api)


    def draw(self):
        return self.drawing_api.draw_circle(self.x, self.y, self.radius)

    def resize_by_percentage(self, percent):
        self.radius *= 1 + percent / 100


class BridgePattern:
    @staticmethod
    def test():
        shapes = [
            CircleShape(1.0, 2.0, 3.0, DrawingAPI1()),
            CircleShape(5.0, 7.0, 11.0, DrawingAPI2()),
            CircleShape(5.0, 4.0, 12.0, DrawingAPI3()),
        ]

        for shape in shapes:
            shape.resize_by_percentage(2.5)
            print(shape.draw())


BridgePattern.test()

Veja também

Referências

links externos