Міст (шаблон проєктування)
Міст (англ. 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: Elements of Reusable Object-Oriented Software [Архівовано 9 листопада 2012 у Wayback Machine.]
Література[ред. | ред. код]
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.