Мост (шаблон проектирования) — Википедия
Мост | |
---|---|
Bridge | |
Тип | структурный |
Описан в Design Patterns | Да |
Шаблон мост (англ. Bridge) — структурный шаблон проектирования, используемый в проектировании программного обеспечения, чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон мост использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.
Цель
[править | править код]При частом изменении класса преимущества объектно-ориентированного подхода становятся очень полезными, позволяя делать изменения в программе, обладая минимальными сведениями о реализации программы. Шаблон мост является полезным там, где часто меняется не только сам класс, но и то, что он делает.
Описание
[править | править код]Когда абстракция и реализация разделены, они могут изменяться независимо. Другими словами, при реализации через шаблон мост, изменение структуры интерфейса не мешает изменению структуры реализации. Рассмотрим такую абстракцию как фигура. Существует множество типов фигур, каждая со своими свойствами и методами. Однако есть что-то, что объединяет все фигуры. Например, каждая фигура должна уметь рисовать себя, масштабироваться и т. п. В то же время рисование графики может отличаться в зависимости от типа ОС, или графической библиотеки. Фигуры должны иметь возможность рисовать себя в различных графических средах, но реализовывать в каждой фигуре все способы рисования или модифицировать фигуру каждый раз при изменении способа рисования непрактично. В этом случае помогает шаблон мост, позволяя создавать новые классы, которые будут реализовывать рисование в различных графических средах. При использовании такого подхода очень легко можно добавлять как новые фигуры, так и способы их рисования.
Связь, изображаемая стрелкой на диаграммах, может иметь 2 смысла: а) «разновидность», в соответствии с принципом подстановки Лисков и б) одна из возможных реализаций абстракции. Обычно в языках используется наследование для реализации как а), так и б), что приводит к разбуханию иерархий классов.
Мост служит именно для решения этой проблемы: объекты создаются парами из объекта класса иерархии А и иерархии B, наследование внутри иерархии А имеет смысл «разновидность» по Лисков, а для понятия «реализация абстракции» используется ссылка из объекта A в парный ему объект B.
Использование
[править | править код]Архитектура Java AWT полностью основана на этом шаблоне — иерархия java.awt.xxx для хэндлов и sun.awt.xxx для реализаций.
Примеры
[править | править код]Пример на C++
[править | править код]#include <iostream> using namespace std; class Drawer { public: virtual void drawCircle(int x, int y, int radius) = 0; }; class SmallCircleDrawer : public Drawer { public: const double radiusMultiplier = 0.25; void drawCircle(int x, int y, int radius) override { cout << "Small circle center " << x << ", " << y << " radius = " << radius*radiusMultiplier << endl; } }; class LargeCircleDrawer : public Drawer { public: const double radiusMultiplier = 10; void drawCircle(int x, int y, int radius) override { cout << "Large circle center " << x << ", " << y << " radius = " << radius*radiusMultiplier << endl; } }; class Shape { protected: Drawer* drawer; public: Shape(Drawer* drw) { drawer = drw; } Shape() {} virtual void draw() = 0; virtual void enlargeRadius(int multiplier) = 0; }; class Circle : public Shape { int x, y, radius; public: Circle(int _x, int _y, int _radius, Drawer* drw) { drawer = drw; setX(_x); setY(_y); setRadius(_radius); } void draw() override { drawer->drawCircle(x, y, radius); } void enlargeRadius(int multiplier) override { radius *= multiplier; } void setX(int _x) { x = _x; } void setY(int _y) { y = _y; } void setRadius(int _radius) { radius = _radius; } }; int main(int argc, char *argv[]) { Shape* shapes[2] = { new Circle(5,10,10, new LargeCircleDrawer()), new Circle(20,30,100, new SmallCircleDrawer())}; for (int i =0 ; i < 2; i++) { shapes[i]->draw(); } return 0; } // Output Large circle center = 5,10 radius = 100 Small circle center = 20,30 radius = 25.0
Пример на Java
[править | править код]public interface Drawer { public void drawCircle(int x, int y, int radius); } public class SmallCircleDrawer implements Drawer{ public static final double radiusMultiplier = 0.25; @Override public void drawCircle(int x, int y, int radius) { System.out.println("Small circle center = " + x + "," + y + " radius = " + radius*radiusMultiplier); } } public class LargeCircleDrawer implements Drawer{ public static final int radiusMultiplier = 10; @Override public void drawCircle(int x, int y, int radius) { System.out.println("Large circle center = " + x + "," + y + " radius = " + radius*radiusMultiplier); } } public abstract class Shape { protected Drawer drawer; protected Shape(Drawer drawer){ this.drawer = drawer; } public abstract void draw(); public abstract void enlargeRadius(int multiplier); } public class Circle extends Shape{ private int x; private int y; private int radius; public Circle(int x, int y, int radius, Drawer drawer) { super(drawer); setX(x); setY(y); setRadius(radius); } @Override public void draw() { drawer.drawCircle(x, y, radius); } @Override public void enlargeRadius(int multiplier) { radius *= multiplier; } public int getX() { return x; } public int getY() { return y; } public int getRadius() { return radius; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setRadius(int radius) { this.radius = radius; } } // Класс, показывающий работу шаблона проектирования "Мост". public class Application { public static void main (String [] args){ Shape [] shapes = { new Circle(5,10,10, new LargeCircleDrawer()), new Circle(20,30,100, new SmallCircleDrawer())}; for (Shape next : shapes) next.draw(); } } // Output Large circle center = 5,10 radius = 100 Small circle center = 20,30 radius = 25.0
Пример на C#
[править | править код]using System; namespace Bridge { // MainApp test application class MainApp { static void Main() { Abstraction ab = new RefinedAbstraction(); // Set implementation and call ab.Implementor = new ConcreteImplementorA(); ab.Operation(); // Change implementation and call ab.Implementor = new ConcreteImplementorB(); ab.Operation(); // Wait for user Console.Read(); } } /// <summary> /// Abstraction - абстракция /// </summary> /// <remarks> /// <li> /// <lu>определяем интерфейс абстракции;</lu> /// <lu>хранит ссылку на объект <see cref="Implementor"/></lu> /// </li> /// </remarks> class Abstraction { // Property public Implementor Implementor { get; set; } public virtual void Operation() { Implementor.Operation(); } } /// <summary> /// Implementor - реализатор /// </summary> /// <remarks> /// <li> /// <lu>определяет интерфейс для классов реализации. Он не обязан точно /// соотведствовать интерфейсу класса <see cref="Abstraction"/>. На самом деле оба /// интерфейса могут быть совершенно различны. Обычно интерфейс класса /// <see cref="Implementor"/> представляет только примитивные операции, а класс /// <see cref="Abstraction"/> определяет операции более высокого уровня, /// базирующиеся на этих примитивах;</lu> /// </li> /// </remarks> abstract class Implementor { public abstract void Operation(); } /// <summary> /// RefinedAbstraction - уточненная абстракция /// </summary> /// <remarks> /// <li> /// <lu>расширяет интерфейс, определенный абстракцией <see cref="Abstraction"/></lu> /// </li> /// </remarks> class RefinedAbstraction : Abstraction { public override void Operation() { Implementor.Operation(); } } /// <summary> /// ConcreteImplementor - конкретный реализатор /// </summary> /// <remarks> /// <li> /// <lu>содержит конкретную реализацию интерфейса <see cref="Implementor"/></lu> /// </li> /// </remarks> class ConcreteImplementorA : Implementor { public override void Operation() { Console.WriteLine("ConcreteImplementorA Operation"); } } // "ConcreteImplementorB" class ConcreteImplementorB : Implementor { public override void Operation() { Console.WriteLine("ConcreteImplementorB Operation"); } } }
Пример на PHP5
[править | править код]interface IPrinter { public function printHeader($textHeader); public function printBody($textBody); } class PdfPrinter implements IPrinter { public function printHeader($textHeader) { echo 'This is your header (' . $textHeader. ') in the pdf file<br>'; } public function printBody($textBody) { echo 'This is your text body (' . $textBody. ') in the pdf file<br>'; } } class ExcelPrinter implements IPrinter { public function printHeader($textHeader) { echo 'This is your header (' . $textHeader. ') in the xls file<br>'; } public function printBody($textBody) { echo 'This is your text body (' . $textBody. ') in the xls file<br>'; } } abstract class Report { protected $printer; public function __construct(IPrinter $printer) { $this->printer = $printer; } public function printHeader($textHeader) { $this->printer->printHeader($textHeader); } public function printBody($textBody) { $this->printer->printBody($textBody); } } class WeeklyReport extends Report { public function print(array $text) { $this->printHeader($text['header']); $this->printBody($text['body']); } } $report = new WeeklyReport(new ExcelPrinter()); $report->print(['header' => 'my header for excel', 'body' => 'my body for excel']); // This is your header (my header for excel) in the xls file</ br>This is your text body (my body for excel) in the xls file<br> $report = new WeeklyReport( new PdfPrinter()); $report->print(['header' => 'my header for pdf', 'body' => 'my body for pdf']); // This is your header (my header for pdf) in the pdf file</ br>This is your text body (my body for pdf) in the pdf file<br>
Пример на PHP5.4
[править | править код]trait TData { private $data; public function __construct(array $data) { $this->data = $data; $this->prepare(); } abstract protected function prepare(); } trait TShow { private $content; public function show() { print $this->content; } } class XmlFormat { use TData, TShow; protected function prepare() { $this->content = '<?xml version="1.1" encoding="UTF-8" ?><root>'; foreach ($this->data as $name => $item) { $this->content .= "<$name>$item</$name>"; } $this->content .= '</root>'; } } class JsonFormat { use TData, TShow; protected function prepare() { $this->content = json_encode($this->data); } } class SelfFormat { use TData, TShow; protected function prepare() { $content = array(); foreach ($this->data as $name => $item) { $string = ''; if (is_string($name)) { $nLen = strlen($name); $string .= "[name|string({$nLen}){{$name}}:val|"; } if (is_int($name)) { $string .= "[index|int{{$name}}:val|"; } if (is_string($item)) { $vLen = strlen($item); $string .= "string($vLen){{$item}"; } if (is_int($item)) { $string .= "int{{$item}"; } $string .= "}]"; array_push($content, $string); } $this->content = 'selfMadeDataFormat:Array('.count($this->data).'):'; $this->content .= implode(',', $content); $this->content .= ':endSelfMadeDataFormat'; } } $xml = new XmlFormat(array('a' => 'b', 'c')); $json = new JsonFormat(array('a' => 'b', 'c')); $self = new SelfFormat(array('a' => 'b', 'c')); $self->show(); /* selfMadeDataFormat:Array(2):[name|string(1){a}:val|string(1){b}],[index|int{0}:val|string(1){c}]:endSelfMadeDataFormat */ $xml->show(); /* <?xml version="1.1" encoding="UTF-8" ?><root><a>b</a><0>c</0></root> */ $json->show(); /* {"a":"b","0":"c"} */
Пример на CoffeeScript
[править | править код]# Implementor class IStorage get : (key) -> set : (key, value) -> # ConcreteImplementor class IFlashStorage extends IStorage # ... # ConcreteImplementor class IJavaStorage extends IStorage # ... # ConcreteImplementor class ISessionStorage extends IStorage # ... # ConcreteImplementor class ICookieStorage extends IStorage # ... # ConcreteImplementor class IGhostStorage extends IStorage # ... # Abstraction class AStorage # protected _implementer : if sessionStorage new ISessionStorage else if navigator.plugins["Shockwave Flash"] new IFlashStorage else if navigator.javaEnabled() new IJavaStorage else if navigator.cookieEnabled new ICookieStorage else new IGhostStorage # public load : (key) -> forgot : (key) -> save : (key, value) -> # RefinedAbstraction class InfoStorage extends AStorage load : (key) -> @_implementer.get("Info:#{key}") save : (key, value) -> @_implementer.set("Info:#{key}", value) forgot : (key) -> @_implementer.set("Info:#{key}", null)
Пример JavaScript
[править | править код]// Implementor ("интерфейс") function Implementor() { this.operation = function() {}; } // ConcreteImplementor (реализация Implementor) function ConcreteImplementorA() { this.operation = function() { alert("ConcreteImplementorA.operation"); }; } ConcreteImplementorA.prototype = Object.create(Implementor.prototype); ConcreteImplementorA.prototype.constructor = ConcreteImplementorA; function ConcreteImplementorB() { this.operation = function() { alert("ConcreteImplementorB.operation"); }; } ConcreteImplementorB.prototype = Object.create(Implementor.prototype); ConcreteImplementorB.prototype.constructor = ConcreteImplementorB; // Abstraction function Abstraction() { var implementor; this.getImplementor = function() { // доступ к implementor'у из RefinedAbstraction return implementor; }; this.setImplementor = function(val) { implementor = val; }; this.operation = function() { implementor.operation(); }; } // RefinedAbstraction function RefinedAbstraction() { var abstr = new Abstraction(); this.setImplementor = function(val) { abstr.setImplementor(val); }; this.operation = function() { abstr.operation(); }; } // использование: var refAbstr = new RefinedAbstraction(); refAbstr.setImplementor( new ConcreteImplementorA() ); refAbstr.operation(); // "ConcreteImplementorA.operation" refAbstr.setImplementor( new ConcreteImplementorB() ); refAbstr.operation(); // "ConcreteImplementorB.operation"
Без необходимости перегрузки методов Abstraction, можно значительно упростить RefinedAbstraction:
function RefinedAbstraction() { Abstraction.call(this); }
Так же можно сохранить ссылки на перегружаемые методы сразу после инстанцирования Abstraction:
function RefinedAbstraction() { Abstraction.call(this); var abstr_setImplementor = this.setImplementor; this.setImplementor = function(val) { abstr_setImplementor(val); }; }
Пример на VB.NET
[править | править код]Namespace Bridge ' Program - Тестовое приложение Class Program Shared Sub Main() Dim AB As Abstraction = New RefinedAbstraction() ' Установить реализацию и вызвать AB.Implementor = New ConcreteImplementorA() AB.Operation() ' Установить реализацию и вызвать AB.Implementor = New ConcreteImplementorB() AB.Operation() ' Ожидать действия пользователя Console.Read() End Sub End Class ''' <summary> ''' Abstraction - абстракция ''' </summary> ''' <remarks> ''' <li> ''' <lu>определяем интерфейс абстракции;</lu> ''' <lu>хранит ссылку на объект <see cref="Implementor"/></lu> ''' </li> ''' </remarks> Class Abstraction Protected m_implementor As Implementor ' Свойство Public Property Implementor() As Implementor Get Return m_implementor End Get Set(ByVal value As Implementor) m_implementor = value End Set End Property Public Overridable Sub Operation() m_implementor.Operation() End Sub End Class ''' <summary> ''' Implementor - реализатор ''' </summary> ''' <remarks> ''' <li> ''' <lu>определяет интерфейс для классов реализации. Он не обязан точно ''' соотведствовать интерфейсу класса <see cref="Abstraction"/>. На самом деле оба ''' интерфейса могут быть совершенно различны. Обычно интерфейс класса ''' <see cref="Implementor"/> представляет только примитивные операции, а класс ''' <see cref="Abstraction"/> определяет операции более высокого уровня, ''' базирующиеся на этих примитивах;</lu> ''' </li> ''' </remarks> MustInherit Class Implementor Public MustOverride Sub Operation() End Class ''' <summary> ''' RefinedAbstraction - уточненная абстракция ''' </summary> ''' <remarks> ''' <li> ''' <lu>расширяет интерфейс, определенный абстракцией <see cref="Abstraction"/></lu> ''' </li> ''' </remarks> Class RefinedAbstraction Inherits Abstraction Public Overrides Sub Operation() implementor.Operation() End Sub End Class ''' <summary> ''' ConcreteImplementor - конкретный реализатор ''' </summary> ''' <remarks> ''' <li> ''' <lu>содержит конкретную реализацию интерфейса <see cref="Implementor"/></lu> ''' </li> ''' </remarks> Class ConcreteImplementorA Inherits Implementor Public Overrides Sub Operation() Console.WriteLine("ConcreteImplementorA Operation") End Sub End Class ' "ConcreteImplementorB" Class ConcreteImplementorB Inherits Implementor Public Overrides Sub Operation() Console.WriteLine("ConcreteImplementorB Operation") End Sub End Class End Namespace
Пример на Python
[править | править код]# Implementor class DrawingAPI: def drawCircle(self, x, y, radius): pass # ConcreteImplementor 1/2 class DrawingAPI1(DrawingAPI): def drawCircle(self, x, y, radius): print "API1.circle at %f:%f radius %f" % (x, y, radius) # ConcreteImplementor 2/2 class DrawingAPI2(DrawingAPI): def drawCircle(self, x, y, radius): print "API2.circle at %f:%f radius %f" % (x, y, radius) # Abstraction class Shape: # Low-level def draw(self): pass # High-level def resizeByPercentage(self, pct): pass # Refined Abstraction class CircleShape(Shape): def __init__(self, x, y, radius, drawingAPI): self.__x = x self.__y = y self.__radius = radius self.__drawingAPI = drawingAPI # low-level i.e. Implementation specific def draw(self): self.__drawingAPI.drawCircle(self.__x, self.__y, self.__radius) # high-level i.e. Abstraction specific def resizeByPercentage(self, pct): self.__radius *= pct def main(): shapes = [ CircleShape(1, 2, 3, DrawingAPI1()), CircleShape(5, 7, 11, DrawingAPI2()) ] for shape in shapes: shape.resizeByPercentage(2.5) shape.draw() if __name__ == "__main__": main()
Литература
[править | править код]- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб.: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1. (также ISBN 5-272-00355-1)
Ссылки
[править | править код]- Паттерн Bridge (Мост) — назначение, описание, реализация (C++) и результаты применения