Міст (шаблон проєктування)
Міст (англ. Bridge) — шаблон проєктування, призначений для того, щоб відділити абстракцію від її конкретної імплементації таким чином, щоб вони могли бути змінені незалежно один від одного. Належить до класу структурних шаблонів.
Призначення
Відокремити абстракцію від її реалізації таким чином, щоб перше та друге можна було змінювати незалежно одне від одного.
Терміни абстракція та реалізацію не мають нічого спільного з абстрактним класом чи інтерфейсом мови програмування. Абстракція — це уявний рівень керування чим-небудь, що не виконує роботу самостійно, а делегує її рівню реалізації.
Мотивація
Якщо для деякої абстракції можливо кілька реалізацій, зазвичай застосовують наслідування. Абстрактний клас визначає інтерфейс абстракції, а його конкретні підкласи по-різному реалізують його. Але такий підхід не завжди є достатньо гнучким. Наслідування жорстко прив'язує реалізацію до абстракції, що перешкоджає незалежній модифікації, розширенню та повторному використанню абстракції та її реалізації.
Застосовність
Слід використовувати шаблон Міст у випадках, коли:
- треба запобігти постійній прив'язці абстракції до реалізації. Так, наприклад, буває коли реалізацію необхідно обрати під час виконання програми;
- як абстракції, так і реалізації повинні розширюватись новими підкласами. У цьому разі шаблон Міст дозволяє комбінувати різні абстракції та реалізації та змінювати їх незалежно одне від одного;
- зміни у реалізації не повинні впливати на клієнтів, тобто клієнтський код не повинен перекомпілюватись;
- треба повністю сховати від клієнтів реалізацію абстракції;
- треба розподілити одну реалізацію поміж кількох об'єктів (можливо застосовуючи підрахунок посилань), і при цьому приховати це від клієнта.
Структура
- Abstraction — абстракція:
- визначає інтерфейс абстракції;
- зберігає посилання на об'єкт типу Implementor;
- RefinedAbstraction — уточнена абстракція:
- розширює інтерфейс, означений абстракцією Abstraction;
- Implementor — реалізатор:
- визначає інтерфейс для класів реалізації. Він не зобов'язаний точно відповідати інтерфейсу класу Abstraction. Насправді обидва інтерфейси можуть бути зовсім різними. Зазвичай, інтерфейс класу Implementor надає тільки примітивні операції, а клас Abstraction визначає операції більш високого рівня, що базуються на цих примітивах;
- ConcreteImplementor — конкретний реалізатор:
- містить конкретну реалізацію інтерфейсу класу Implementor.
Коли існує тільки одна реалізація, то в 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
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
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
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
"""
Приклад шаблону міст.
"""
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.