Wzór mostka - Bridge pattern

Wzór most jest wzorzec projektowania stosowane w inżynierii oprogramowania , które jest przeznaczone do „DECOUPLE w abstrakcji od jego wdrożenia , tak że dwa mogą się zmieniać niezależnie” , wprowadzonym przez Gang of Four . Most używa enkapsulacji , agregacji i może użyć dziedziczenia do odrębnych zadań w różnych klasach .

Kiedy klasa zmienia się często, cechy programowania obiektowego stają się bardzo użyteczne, ponieważ zmiany w programie „s kod może być łatwo wykonane z wcześniejszego minimalną wiedzą na temat programu. Wzorzec mostka jest przydatny, gdy zarówno klasa, jak i to, co robi, często się różnią. Samą klasę można traktować jako abstrakcję, a o tym, co klasa może zrobić jako implementację . Wzorzec mostu można również traktować jako dwie warstwy abstrakcji.

Gdy istnieje tylko jedna ustalona implementacja, ten wzorzec jest znany jako idiom Pimpl w świecie C ++ .

Wzorzec mostu jest często mylony ze wzorcem adaptera i często jest implementowany przy użyciu wzorca adaptera obiektu ; np. w poniższym kodzie Java.

Wariant: implementację można jeszcze bardziej oddzielić, odkładając jej obecność do momentu wykorzystania abstrakcji.

Przegląd

Wzorzec projektowy Bridge jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych GoF, które opisują, jak rozwiązywać powtarzające się problemy projektowe w celu projektowania elastycznego i wielokrotnego użytku oprogramowania obiektowego, to znaczy obiektów, które są łatwiejsze do wdrożenia, zmiany, testowania i ponowne użycie.

Jakie problemy może rozwiązać wzorzec projektu mostu?

  • Abstrakcja i jej realizacja powinny być definiowane i rozszerzane niezależnie od siebie.
  • Należy unikać wiązania czasu kompilacji między abstrakcją a jej implementacją, aby można było wybrać implementację w czasie wykonywania.

Podczas korzystania z podklas różne podklasy implementują klasy abstrakcyjne na różne sposoby. Ale implementacja jest związana z abstrakcją w czasie kompilacji i nie można jej zmienić w czasie wykonywania.

Jakie rozwiązanie opisuje wzorzec projektowy Bridge?

  • Oddziel abstrakcję ( Abstraction ) od jej implementacji ( Implementor ), umieszczając je w osobnych hierarchiach klas.
  • Zaimplementuj Abstraction w zakresie (delegując do) Implementor obiekt.

Umożliwia to skonfigurowanie obiektu Abstraction z Implementor obiektem w czasie wykonywania.
Zobacz także poniższą klasę i diagram sekwencji Unified Modeling Language .

Struktura

Diagram klas i sekwencji UML

Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Bridge.

Na powyższym diagramie klas Unified Modeling Language abstrakcja ( Abstraction ) nie jest zaimplementowana jak zwykle w pojedynczej hierarchii dziedziczenia. Zamiast tego istnieje jedna hierarchia dla abstrakcji ( Abstraction ) i oddzielna hierarchia dla jej implementacji ( Implementor ), co sprawia, że ​​są one niezależne od siebie. Abstraction Interfejs ( operation() ) jest realizowany w kategoriach (poprzez delegowanie na) Implementor interfejs ( imp.operationImp() ).
W UML sekwencji diagram przedstawia interakcje wykonywania czasu: Abstraction1 realizacji przekazuje obiekt do Implementor1 przedmiotu (wywołując operationImp() w Implementor1 ), który wykonuje operację i przywraca Abstraction1 .

Diagram klas

Mostek klasy UML diagram.svg

Abstrakcja (klasa abstrakcyjna)
definiuje abstrakcyjny interfejs
zachowuje odniesienie do implementatora.
RefinedAbstraction (klasa normalna)
rozszerza interfejs zdefiniowany przez Abstraction
Implementator (interfejs)
definiuje interfejs dla klas implementacyjnych
ConcreteImplementor (klasa normalna)
implementuje interfejs Implementor

Przykład

DO#

Wzorzec mostka tworzy obiekty w strukturze drzewa. Oddziela abstrakcję od implementacji. Tutaj abstrakcja reprezentuje klienta, z którego będą wywoływane obiekty. Poniżej podano przykład zaimplementowany w języku C #

// 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();
    }
}

Klasy Bridge to implementacja, która używa tej samej architektury zorientowanej na interfejs do tworzenia obiektów. Z drugiej strony abstrakcja pobiera instancję klasy implementacji i uruchamia jej metodę. W ten sposób są całkowicie od siebie oddzielone.

Kryształ

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

Wynik

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;
}

Wynik:

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

Jawa

Poniższy program w języku Java definiuje konto bankowe, które oddziela operacje na koncie od rejestrowania tych operacji.

// 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);
    }
}

Wyświetli:

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();

Wynik:

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()			
      }	
  }
}

Pyton

"""
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()

Zobacz też

Bibliografia

Linki zewnętrzne