Метафора
Представим ситуацию, когда вам требуется работать на разных автомобилях, однако садясь в новый автомобиль вам уже желательно знать, как им управлять. Таким образом, Вы сталкиваетесь с паттерном «мост». С одной стороны, вы имеете множество различных автомобилей (разные модели и марки), но среди них есть общая абстракция (интерфейс) в виде руля, педалей, коробки передач и так далее. Таким образом, мы задаем как-бы правила изготовления автомобилей, по которым мы можем создавать любые их виды, но за счет сохранения общих правил взаимодействия с ними, мы можем одинаково управлять каждым из них. «Мостом» в данном случае является пара двух «объектов»: конкретного автомобиля и правил взаимодействия с этим (и любым другим) автомобилем.
Назначение
Позволяет отделить абстракцию от реализации так, что и то, и другое можно было бы изменять независимо, путём помещения абстракции и реализации в отдельные иерархии классов.
Представим некоторую классическую схему проектирования классов – есть базовый абстрактный класс, определяющий интерфейс и его наследники, реализующие данный интерфейс. У данной схемы есть определенные недостатки:
- Реализация жестко привязана к интерфейсу, изменить реализацию объекта во время выполнения уже нельзя.
- Если число наследников велико, систему становится сложно поддерживать.
Используя паттерн мост, мы избавимся от этих недостатков.
Диаграмма
Пример
Предположим у нас с Вами есть некоторые классы для формирования PDF отчётов: OneReport, TwoReport:
Каждый из них наследуется от базового класса Report и переопределяет методы get_data и write_data, при этом write_data формирует PDF формуляр, а get_data содержит бизнес-логику получения данных для вывода.
В один прекрасный момент к нам приходит клиент и говорит: «Хочу вывод в DOC и в XML». Самое первое что приходит в голову это наследование:
Вывод PDF мы уберём из отчётов. Создадим по наследнику от каждого отчёта и переопределим метод вывода отчёта для конкретного формата. Как понятно из схемы, у нас появилась другая проблема — сложность поддержки большого числа классов.
Решить её мы можем, несколько изменив наше решение, применив паттерн мост:
Реализацию, отвечающую за вывод данных, мы вынесли в отдельную иерархию классов — Printer. В клиенте при определении объекта OneReport или TwoReport мы будет внедрять ему реализацию конкретного принтера, который умеет печатать в нужном клиенту формате.
Пример на ABAP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
*&---------------------------------------------------------------------* *& Определение классов шаблона *&---------------------------------------------------------------------* CLASS lcl_printer DEFINITION DEFERRED. CLASS lcl_report DEFINITION ABSTRACT. PUBLIC SECTION. METHODS: print_report, constructor IMPORTING io_printer TYPE REF TO lcl_printer. PROTECTED SECTION. DATA: mv_data TYPE string, mo_printer TYPE REF TO lcl_printer. METHODS: get_data ABSTRACT, write_data ABSTRACT. ENDCLASS. CLASS lcl_report IMPLEMENTATION. METHOD constructor. mo_printer = io_printer. ENDMETHOD. METHOD print_report. me->get_data( ). me->write_data( ). ENDMETHOD. ENDCLASS. CLASS lcl_report_one DEFINITION INHERITING FROM lcl_report. PROTECTED SECTION. METHODS: get_data REDEFINITION, write_data REDEFINITION. ENDCLASS. CLASS lcl_report_two DEFINITION INHERITING FROM lcl_report. PROTECTED SECTION. METHODS: get_data REDEFINITION, write_data REDEFINITION. ENDCLASS. CLASS lcl_printer DEFINITION ABSTRACT. PUBLIC SECTION. METHODS: write_data ABSTRACT IMPORTING iv_data TYPE string. ENDCLASS. CLASS lcl_report_one IMPLEMENTATION. METHOD get_data. mv_data = 'REP1 DATA'. ENDMETHOD. METHOD write_data. mo_printer->write_data( mv_data ). ENDMETHOD. ENDCLASS. CLASS lcl_report_two IMPLEMENTATION. METHOD get_data. mv_data = 'REP2 DATA'. ENDMETHOD. METHOD write_data. mo_printer->write_data( mv_data ). ENDMETHOD. ENDCLASS. CLASS lcl_printer_pdf DEFINITION INHERITING FROM lcl_printer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. CLASS lcl_printer_pdf IMPLEMENTATION. METHOD write_data. WRITE: / 'PDF: ', iv_data. ENDMETHOD. ENDCLASS. CLASS lcl_printer_xml DEFINITION INHERITING FROM lcl_printer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. CLASS lcl_printer_xml IMPLEMENTATION. METHOD write_data. WRITE: / 'XML: ', iv_data. ENDMETHOD. ENDCLASS. CLASS lcl_printer_doc DEFINITION INHERITING FROM lcl_printer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. CLASS lcl_printer_doc IMPLEMENTATION. METHOD write_data. WRITE: / 'DOC: ', iv_data. ENDMETHOD. ENDCLASS. *&---------------------------------------------------------------------* *& Работа с шаблоном *&---------------------------------------------------------------------* START-OF-SELECTION. DATA(lo_rep_one) = NEW lcl_report_one( io_printer = NEW lcl_printer_pdf( ) ). DATA(lo_rep_two) = NEW lcl_report_two( io_printer = NEW lcl_printer_doc( ) ). lo_rep_one->print_report( ). lo_rep_two->print_report( ). |
При реализации классов отчетов нет смысла переопределять метод write_data(), т.к. функционал у него один и тот же. Не проще ли его оставить на уровне родительского отчета? Или это для наглядности сделано?
В данном случае не нужно, т.к. логика не отличается.