Понимание шаблона проектирования моста

24

Я вообще не понимаю шаблон проектирования "мост". Я прошел через различные веб-сайты, но они не помогли.

Кто-нибудь может помочь мне понять это?


источник
2
Я тоже этого не понимаю. Ждем с нетерпением ответов :)
Фиолетовый Жираф
Существует множество сайтов и книг, описывающих шаблоны дизайна. Я не думаю, что стоит повторять то, что уже написано, может быть, вы можете задать конкретный вопрос. Мне потребовалось некоторое время, чтобы понять это, продолжая переключаться между различными источниками и примерами.
Елена

Ответы:

16

В ООП мы используем полиморфизм, поэтому абстракция может иметь несколько реализаций. Давайте посмотрим на следующий пример:

//trains abstraction
public interface Train
{ 
    move();
}
public class MonoRail:Train
{
    public override move()
    {
        //use one track;
    }
}
public class Rail:Train
{
    public override move()
    {
        //use two tracks;
    }
}

Введено новое требование, и оно должно учитывать перспективу ускорения поездов, поэтому измените код, как показано ниже.

    public interface Train
    { 
        void move();
    }
    public class MonoRail:Train
    {
        public override void move()
        {
            //use one track;
        }
    }
    public class ElectricMonoRail:MonoRail
    {
        public override void move()
        {
            //use electric engine on one track.
        }
    }
    public class DieselMonoRail: MonoRail
    {
        public override void move()
        {
            //use diesel engine on one track.
        }
    }
    public class Rail:Train
    {
        public override void move()
        {
            //use two tracks;
        }
    }
    public class ElectricRail:Rail
    {
        public override void move()
        {
            //use electric engine on two tracks.
        }
    }
    public class DieselRail: Rail
    {
        public override void move()
        {
            //use diesel engine on two tracks.
        }
    }

Приведенный выше код не подлежит сопровождению и не имеет возможности многократного использования (при условии, что мы могли бы повторно использовать механизм ускорения для той же платформы трека). Следующий код применяет шаблон моста и разделяет две разные абстракции: поездный транспорт и ускорение .

public interface Train
{ 
    void move(Accelerable engine);
}
public interface Accelerable
{
    public void accelerate();
}
public class MonoRail:Train
{
    public override void move(Accelerable engine)
    {
        //use one track;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class Rail:Train
{
    public override void move(Accelerable engine)
    {
        //use two tracks;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class ElectricEngine:Accelerable{/*implementation code for accelerable*/}
public class DieselEngine:Accelerable{/*implementation code for accelerable*/}
Thurein
источник
3
Очень хороший пример. Я бы добавил свои два цента: это очень хороший пример предпочтения композиции перед наследованием
zzfima
1
Что бы это ни стоило, я думаю, что так и должно быть, Monorailтак как на самом деле это не два слова, это одно (составное) слово. MonoRail будет некоторым подклассом Rail вместо другого вида рельса (который он есть). Так же, как мы не будем использовать SunShineили CupCake, они будут SunshineиCupcake
ErikE
Эта строка «две разные абстракции, железнодорожный транспорт и ускорение» помогает указать, что представляет собой две иерархии, и что мы пытаемся решить, так это заставить их различаться независимо друг от друга.
wangdq
11

Хотя у большинства шаблонов проектирования есть полезные имена, я считаю, что имя «Мост» не является интуитивно понятным в отношении того, что оно делает.

Концептуально, вы помещаете детали реализации, используемые иерархией классов, в другой объект, обычно со своей собственной иерархией. Тем самым вы устраняете тесную зависимость от этих деталей реализации и позволяете изменить детали этой реализации.

В небольшом масштабе я уподобляю это использованию шаблона стратегии, чтобы вы могли подключить новое поведение. Но вместо того, чтобы просто обернуть алгоритм, как это часто наблюдается в стратегии, объект реализации обычно более функциональный. И когда вы применяете эту концепцию ко всей иерархии классов, большая модель становится мостовой. (Опять ненавижу имя).

Это не шаблон, который вы будете использовать каждый день, но я нашел его полезным при управлении потенциальным взрывом классов, который может произойти, когда у вас есть (очевидная) потребность в множественном наследовании.

Вот пример из реальной жизни:

У меня есть инструмент RAD, который позволяет вам удалять и настраивать элементы управления в области проектирования, поэтому у меня есть объектная модель, подобная этой:

Widget // base class with design surface plumbing
+ Top
+ Left
+ Width
+ Height
+ Name
+ SendToBack
+ BringToFront
+ OnPropertyEdit
+ OnSelect
+ Validate
+ ShowEditor
+ Paint
+ Etc

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor // override base to show a property editor form specific to a Textbox
+ Paint // override to render a textbox onto the surface    
+ Etc

ListWidget : Widget // list specific
+ Items
+ SelectedItem
+ ShowEditor // override base to show a property editor form specific to a List
+ Paint // override to render a list onto the surface
+ Etc

И так далее, возможно, с десяток элементов управления.

Но затем добавляется новое требование для поддержки нескольких тем (look-n-feels). Скажем , у нас есть следующие темы: Win32, WinCE, WinPPC, WinMo50, WinMo65. Каждая тема будет иметь разные значения или реализации для операций, связанных с рендерингом, таких как DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb и т. Д.

Я мог бы создать объектную модель, как это:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

и т. д. для одного типа управления

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

и т. д., для каждого типа управления (снова)

Вы поняли - вы получаете классовое количество # виджетов, умноженное на # тем. Это усложняет RAD-дизайнер, заставляя его осознавать каждую тему. Кроме того, добавление новых тем заставляет модифицировать RAD-дизайнер. Кроме того, в теме есть много общих реализаций, которые было бы здорово наследовать, но элементы управления уже наследуются от общей базы ( Widget).

Поэтому вместо этого я создал отдельную иерархию объектов, которая реализует тему. Каждый виджет будет содержать ссылку на объект, который реализует операции рендеринга. Во многих текстах к этому классу добавляется суффикс, Implно я отклонился от этого соглашения об именах.

Так что теперь моя TextboxWidgetвыглядит так:

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc

И я могу сделать так, чтобы мои разные художники унаследовали мою тематическую базу, чего я не мог сделать раньше:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

Одним из приятных моментов является то, что я могу динамически загружать реализации во время выполнения, что позволяет мне добавлять столько тем, сколько я хочу, без изменения основного программного обеспечения. Другими словами, моя «реализация может варьироваться независимо от абстракции».

tcarvin
источник
Я не понимаю, как это должно быть образцом моста? Когда вы добавляете новый компонент в иерархию виджетов, вы вынуждены добавлять этот новый виджет ко ВСЕМ художникам (Win32NewWidgetPainter, PPCNewWidgetPainter). Это НЕ две независимо растущие иерархии. Для правильного паттерна моста вы не должны создавать подкласс класса PlatformWidgetPainter для каждого виджета, вместо этого он должен получить виджет "дескриптор рисования".
Мазёд
Спасибо за отзыв, вы правы. Это было много лет назад, когда я опубликовал это, и сейчас, рассмотрев его, я бы сказал, что он хорошо описывает мост до последнего момента, откуда я получил Win32TextboxPainterи Win32ListPainter откуда Win32WidgetPainter. Вы можете иметь дерево наследования на стороне реализации, но оно должно быть более общим (возможно StaticStyleControlPainter, EditStyleControlPainterи ButtonStyleControlPainter) с любыми необходимыми примитивными операциями переопределены при необходимости. Это ближе к реальному коду, на котором я основывал пример.
tcarvin
3

Мост намеревается отделить абстракцию от своей конкретной реализации , так что оба могут варьироваться независимо:

  • усовершенствовать абстракцию с помощью подклассов
  • обеспечить различные реализации, в том числе путем создания подклассов, без необходимости знать ни абстракцию, ни ее уточнения.
  • при необходимости выберите во время выполнения, которая является наиболее подходящей реализацией .

Мост достичь этого с помощью композиции:

  • абстракция ссылается (ссылка или указатель) на объект реализации
  • абстракция и ее доработки известны только интерфейсу реализации

введите описание изображения здесь

Дополнительные замечания о частой путанице

Этот шаблон очень похож на шаблон адаптера: абстракция предлагает другой интерфейс для реализации и использует композицию для этого. Но:

Ключевое различие между этими шаблонами заключается в их намерениях
- Gamma & al, в « Шаблонах проектирования, элемент программного обеспечения многократного использования OO » , 1995

В этой превосходной оригинальной книге по шаблонам проектирования авторы также отмечают, что:

  • Адаптеры часто используются при обнаружении несовместимости и непредвиденной связи
  • Мосты используются с самого начала проектирования, когда ожидается, что классы будут развиваться независимо.
Christophe
источник