Міст (шаблон проєктування)Міст (англ. Bridge) — шаблон проєктування, призначений для того, щоб відділити абстракцію від її конкретної імплементації таким чином, щоб вони могли бути змінені незалежно один від одного. Належить до класу структурних шаблонів. ПризначенняВідокремити абстракцію від її реалізації таким чином, щоб перше та друге можна було змінювати незалежно одне від одного. Терміни абстракція та реалізацію не мають нічого спільного з абстрактним класом чи інтерфейсом мови програмування. Абстракція — це уявний рівень керування чим-небудь, що не виконує роботу самостійно, а делегує її рівню реалізації. МотиваціяЯкщо для деякої абстракції можливо кілька реалізацій, зазвичай застосовують наслідування. Абстрактний клас визначає інтерфейс абстракції, а його конкретні підкласи по-різному реалізують його. Але такий підхід не завжди є достатньо гнучким. Наслідування жорстко прив'язує реалізацію до абстракції, що перешкоджає незалежній модифікації, розширенню та повторному використанню абстракції та її реалізації. ЗастосовністьСлід використовувати шаблон Міст у випадках, коли:
Структура
Коли існує тільки одна реалізація, то в C++ цей шаблон називається Pimpl. Переваги та недолікиПереваги
Недоліки
ВідносиниОб'єкт Abstraction містить у собі Implementor і перенаправляє йому запити клієнта. Зв'язок з іншими патернами
РеалізаціяC++Приклад реалізації мовою С++
#include <iostream>
using namespace std;
// ієрархія реалізації
struct IWallCreator
{
virtual void BuildWall() = 0;
virtual void BuildWallWithDoor() = 0;
virtual void BuildWallWithWindow() = 0;
};
// конкретні реалізації
struct BrickWallCreator :public IWallCreator
{
virtual void BuildWall(){ cout << "Brick & mortar wall.\n"; }
virtual void BuildWallWithDoor(){ cout << "Brick & mortar wall with door hole.\n"; }
virtual void BuildWallWithWindow(){ cout << "Brick & mortar wall with window hole.\n";}
};
struct FoamblockWallCreator :public IWallCreator
{
virtual void BuildWall() { cout << "Foam wall.\n"; }
virtual void BuildWallWithDoor() { cout << "Foam wall.\n"; }
virtual void BuildWallWithWindow() { cout << "Foam wall.\n"; }
};
// базовий клас абстракції
class IBuildCompany
{
protected:
IWallCreator* wallCreator; // міст
public:
void setWallCreator(IWallCreator* creator)
{
wallCreator = creator;
}
virtual void BuildFoundation() = 0;
virtual void BuildRoom() = 0;
virtual void BuildRoof() = 0;
};
// конкретні абстракції
struct TownBuildCompany :public IBuildCompany
{
virtual void BuildFoundation(){ cout << "Concrete solid foundation is finished.\n"; }
// визначення методів абстракції через методи реалізацї
virtual void BuildRoom()
{
wallCreator->BuildWallWithWindow();
wallCreator->BuildWall();
wallCreator->BuildWall();
wallCreator->BuildWallWithDoor();
cout << "Room is finished.\n";
}
virtual void BuildRoof() { cout << "Flat roof is finished.\n";}
};
struct CoountryBuildCompany :public IBuildCompany
{
virtual void BuildFoundation() { cout << "Country foundation is finished.\n"; }
virtual void BuildRoom()
{
wallCreator->BuildWallWithDoor();
wallCreator->BuildWallWithWindow();
wallCreator->BuildWallWithWindow();
wallCreator->BuildWall();
cout << "Room is finished.\n";
}
virtual void BuildRoof() { cout << "Roof is finished.\n"; }
};
void main()
{
BrickWallCreator brigade;
FoamblockWallCreator team;
TownBuildCompany Avalon;
CoountryBuildCompany Riel;
cout << "*Avalon* has started the building!\n\n";
Avalon.BuildFoundation();
Avalon.setWallCreator(&team);
Avalon.BuildRoom();
Avalon.BuildRoom();
cout << " the creator of walls was changed\n";
Avalon.setWallCreator(&brigade);
Avalon.BuildRoom();
Avalon.BuildRoof();
cout << "\n*Riel* has started the building!\n\n";
Riel.BuildFoundation();
Riel.setWallCreator(&brigade);
Riel.BuildRoom();
Riel.BuildRoom();
Riel.BuildRoof();
}
C#Приклад реалізації мовою С#
namespace Bridge
{
// нехай необхідно реалізувати сховище даних для покупця (Buyer) та замовника (Client),
// при чому сховища можуть використовувати як базу даних так і файлову систему
// наївна реалізація передбачає створення класу під кожний функціонал
// із появою у системі нового типу користувачів чи нового способу збереження
// нам доведеться додавати велику кількість класів у ієрархію
abstract class StorageBase { . . . }
class BuyerDataBaseStorage : StorageBase { . . . }
class BuyerFileStorage : StorageBase { . . . }
class ClientDataBaseStorage : StorageBase { . . . }
class ClientFileStorage : StorageBase { . . . }
// даний шаблон пропонує розділити незалежну функціональність (тип користсувача та спосіб збереження) на окремі ієрархії класів
// абстракція - ієрархія класів, яка делегує завдання іншій ієрархії
// реалізація - ієрархія класів, яка відповідальна за виконнання завдання
// ієрархія абстракцій
// описує типи користувачів
abstract class ContainerBase
{
StorageBase storage;
public void SetStorage(StorageBase storage)
{
this.storage = storage;
}
public void Add(object entity)
{
// абстракція делегує роботу реалізації
this.storage.Add(entity);
}
}
class BuyerContainer : ContainerBase { . . . }
class ClientContainer : ContainerBase { . . . }
// ієрархія реалізації
// описує способи збереження
abstract class StorageBase
{
public abstract void Add(object entity);
}
class DataBaseStorage : StorageBase { . . . }
class FileStorage : StorageBase { . . . }
// якщо раніше кількість класів у ієрархії становила типи клієнтів * способи збереження (A * B)
// то тепер — типи клієнтів + способи збереження (A + B)
}
CrystalПриклад реалізації мовою Crystal
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
Результат: API1.circle at 1.0:2.0 - radius: 3.075 API2.circle at 5.0:7.0 - radius: 11.275 PHPПриклад реалізації мовою 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();
Результат: API1.circle at 1:3 radius 17.5 API2.circle at 5:7 radius 27.5 ScalaПриклад реалізації мовою 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()
}
}
}
PythonПриклад реалізації мовою Python
"""
Приклад шаблону міст.
"""
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()
Джерела
ЛітератураАлан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.
|