Мост (шаблон проектирования)
Шаблон мост (англ. Bridge) — структурный шаблон проектирования, используемый в проектировании программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон мост использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.
Мост | |
---|---|
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)