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