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
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
- 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
- Bridge w UML i LePUS3 (formalny język modelowania)
- Wzorce projektowe C #: wzorzec mostu . Przykładowy rozdział . 2002-12-20. Od: James W. Cooper (2003). Wzorce projektowe C #: samouczek . Addison-Wesley . ISBN 0-201-84453-2 .