1. 目的
将抽象部分与它的实现部分分离,使得它们都可以独立地变化。
桥接模式(Bridge)也称:柄体模式(Handle and Body)或接口模式(Interface)。
2. 解读
类比:
- 先举一个生活中的例子,比如“蜡笔”。如果我们需要12种颜色,那么我们准备12支对应颜色的蜡笔即可。但如果我们需要大、中、小号的三种蜡笔,并且都要有12种颜色,我们可能不得不买3盒不同型号的蜡笔,并且每盒都包含12种颜色。
- 有没有其它的思路呢?如果是毛笔呢?可不可以准备大、中、小三只毛笔,然后配上12种颜色盒呢? 两种方案有什么不同?在这个场景中,如果使用蜡笔,我们将需要准备 3 * 12, 共计36支。但若使用毛笔,则只需要准备3支 + 12种对应的颜料盒即可。哈哈,找到不同点了么?😉
- 在这个例子中其实包含了两个维度:型号与颜色。对于蜡笔,型号与颜色是“强耦合”关系,型号与颜色的变化是绑定在一起的;但对于毛笔,型号与颜色是“弱耦合”关系,颜色维度(颜料盒)和型号(毛笔大小)维度可以独立变化。
由上面的例子,可以引出我们面向对象世界中的一个重要模式,也即桥接模式(Bridge Pattern),这个模式的核心点在于分离不同维度的变化。例如之前提及到的预留出口类模式,就是桥接模式的一个典型应用场景。在本篇中,我们将进一步阐释这个模式的内核思想。
应用场景:
- 在某些场景中,当对象有多个维度的变化(最典型的场景就是2个维度),例如抽象/平台、前端/后端等,则可以使用桥接模式。桥接模式使得对象的其中任何一个维度都可以相互结合使用。
- 例如,不同操作系统下,对于不同图片格式的处理和显示。可以使用桥接模式,达成操作系统维度和图片处理维度的分离。
- 再举一个实际的例子,比如,在不同国家会计准则下,企业税费的计算与报表出具,这个场景下的2个维度即“报表规范” 和“税费的计算”,分别基于这连个维度建模,并使用桥接模式让连个对象实现“聚合”,组成“松耦合”的架构。
- 此模式的核心在于,在建模过程中,充分理解和应用“聚合/合成”原则。
类图:
各种角色:
- Abstraction - 抽象,定义了客户端可以访问的抽象接口(例如:定义的图片显示接口),并引用Implementor的实例对象。
- RefinedAbstraction - 被提炼的抽象,可进一步扩展和丰富Abstraction的行为(例如:实现abstraction接口,并丰富在不同操作系统下,对于显卡的驱动动作)
- Implementor - 实现,为具体的实现定义了抽象的接口行为。(例如:图片显示)
- ConcreteImplementor - 具体的实现,实现具体的功能。(例如:jpg图片的显示,png图片的显示)
注意:通常情况下,抽象Abstraction部分的接口和实现Implementor部分的接口是不同的, 抽象Abstraction的接口是面向客户端Client的,实现Implementor部分的接口是面向具体的实现对象的。
要点:
- “桥接模式” 的优点在于,其分离的抽象和实现之间的耦合关系,让两个维度可以独立的变化,提升的架构的灵活性。
- 当然,“桥接模式” 的引入增加了系统设计的复杂性,需要更多的effort。同时,这种间接的引用关系,在一定程度上也对performance带来一些影响。
体现的设计原则:
- 单一职责原则 - 按维度分工,职责单一而清晰
- 开放封闭原则 - 不同的维度间,可以独立扩展
- 除此之外,桥接模式是合成/聚合复用原则的一个典型应用。“合成/聚合复用原则”告诉我们,要尽量使用合成/聚合的关系去完成设计,尽量不要使用类的继承。因为继承是一种“强依赖”关系,父类的变动会直接影响子类的变化,基于继承关系建立模型,如若使用子类时,解决不了新的问题,则父类就会需要被重写、或重新设计,这种“强依赖”关系限制了面向对象的灵活性,并最终限制了复用性。
- “合成/聚合复用原则”的好处在于,优先使用对象的合成/聚合将有助于保持每个类的封装,并被集中到单个任务上。这样的类和类的继承层次会保持较小的规模,并且不太可能增长成为不可控制的庞然大物。
设计模式辨析:
- 适配器模式:适配器是从一些现有组件的接口中解耦一个抽象。 相反,桥模式通过设计提前实现解耦,并考虑到未来的可扩展性。 通常,适配器和Adaptee的接口有点相似,而桥模式则并非如此。
3. 举例
3.1 桥接模式的典型代码
下面给出了“桥接模式”的架构代码,abstraction与Implementor进行了解耦合,通过合成关系(桥接)进行弱关联。
REPORT zbridge_pattern.
**********************************************************************
* abstraction per dimensions
**********************************************************************
INTERFACE lif_implementor.
METHODS do_operation_1.
METHODS do_operation_2.
ENDINTERFACE.
CLASS lcl_abstraction DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS set_implementor
IMPORTING
io_imp TYPE REF TO lif_implementor.
METHODS do_action_1 ABSTRACT.
METHODS do_action_2 ABSTRACT.
PROTECTED SECTION.
DATA mo_implementor TYPE REF TO lif_implementor.
ENDCLASS.
CLASS lcl_abstraction IMPLEMENTATION.
METHOD set_implementor.
mo_implementor = io_imp.
ENDMETHOD.
ENDCLASS.
**********************************************************************
* implementation
**********************************************************************
CLASS lcl_implementor_a DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_implementor.
ENDCLASS.
CLASS lcl_implementor_a IMPLEMENTATION.
METHOD lif_implementor~do_operation_1.
WRITE / 'Do operation 1 in implementor A' .
ENDMETHOD.
METHOD lif_implementor~do_operation_2.
WRITE / 'Do operation 2 in implementor A' .
ENDMETHOD.
ENDCLASS.
CLASS lcl_implementor_b DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_implementor.
ENDCLASS.
CLASS lcl_implementor_b IMPLEMENTATION.
METHOD lif_implementor~do_operation_1.
WRITE / 'Do operation 1 in implementor B' .
ENDMETHOD.
METHOD lif_implementor~do_operation_2.
WRITE / 'Do operation 2 in implementor B' .
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_refined_abstraction_x DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_abstraction.
PUBLIC SECTION.
METHODS:
do_action_1 REDEFINITION,
do_action_2 REDEFINITION.
ENDCLASS.
CLASS lcl_refined_abstraction_x IMPLEMENTATION.
METHOD do_action_1.
WRITE / 'Do action 1 in abstraction X'.
mo_implementor->do_operation_1( ).
ENDMETHOD.
METHOD do_action_2.
WRITE / 'Do action 2 in abstraction X'.
mo_implementor->do_operation_2( ).
ENDMETHOD.
ENDCLASS.
CLASS lcl_refined_abstraction_y DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_abstraction.
PUBLIC SECTION.
METHODS:
do_action_1 REDEFINITION,
do_action_2 REDEFINITION.
ENDCLASS.
CLASS lcl_refined_abstraction_y IMPLEMENTATION.
METHOD do_action_1.
WRITE / 'Do action 1 in abstraction Y'.
mo_implementor->do_operation_1( ).
ENDMETHOD.
METHOD do_action_2.
WRITE / 'Do action 2 in abstraction Y'.
mo_implementor->do_operation_2( ).
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
DATA(lo_abstraction_x) = NEW lcl_refined_abstraction_x( ).
DATA(lo_abstraction_y) = NEW lcl_refined_abstraction_y( ).
DATA(lo_imp_a) = NEW lcl_implementor_a( ).
DATA(lo_imp_b) = NEW lcl_implementor_b( ).
lo_abstraction_x->set_implementor( lo_imp_a ).
lo_abstraction_x->do_action_1( ).
lo_abstraction_x->do_action_2( ).
lo_abstraction_y->set_implementor( lo_imp_b ).
lo_abstraction_y->do_action_1( ).
lo_abstraction_y->do_action_2( ).
运行结果:
可以看到,Abstraction X与 Implementor A进行组合,Abstraction Y与 Implementor B进行组合。当然,也可以直接实现,Abstraction X与 Implementor B进行组合,Abstraction Y与 Implementor A进行组合。
这就是桥接模式带来的灵活性。
3.2 图片显示与操作系统的分离
下面给出了一个具体的例子,分离“图片显示”和“操作系统”两个维度,从而使得两个维度可以独立变化。
REPORT zbridge_pattern2.
**********************************************************************
* abstraction per dimensions
**********************************************************************
INTERFACE lif_image_imp.
METHODS display.
ENDINTERFACE.
CLASS lcl_image DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS set_image_implementor
IMPORTING
io_imp TYPE REF TO lif_image_imp.
METHODS output_image ABSTRACT.
PROTECTED SECTION.
DATA mo_image_imp TYPE REF TO lif_image_imp.
ENDCLASS.
CLASS lcl_image IMPLEMENTATION.
METHOD set_image_implementor.
mo_image_imp = io_imp.
ENDMETHOD.
ENDCLASS.
**********************************************************************
* implementation
**********************************************************************
CLASS lcl_image_imp_jpg DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_image_imp.
ENDCLASS.
CLASS lcl_image_imp_jpg IMPLEMENTATION.
METHOD lif_image_imp~display.
WRITE / 'Display a .jpg file' .
ENDMETHOD.
ENDCLASS.
CLASS lcl_image_imp_png DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_image_imp.
ENDCLASS.
CLASS lcl_image_imp_png IMPLEMENTATION.
METHOD lif_image_imp~display.
WRITE / 'Display a .png file' .
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_image_windows DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_image.
PUBLIC SECTION.
METHODS: output_image REDEFINITION.
ENDCLASS.
CLASS lcl_image_windows IMPLEMENTATION.
METHOD output_image.
WRITE / 'Prepare windows system environment...'.
mo_image_imp->display( ).
ENDMETHOD.
ENDCLASS.
CLASS lcl_image_linux DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_image.
PUBLIC SECTION.
METHODS: output_image REDEFINITION.
ENDCLASS.
CLASS lcl_image_linux IMPLEMENTATION.
METHOD output_image.
WRITE / 'Prepare Linux system environment...'.
mo_image_imp->display( ).
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
DATA(lo_image_windows) = NEW lcl_image_windows( ).
DATA(lo_image_linux) = NEW lcl_image_linux( ).
DATA(lo_image_jpg) = NEW lcl_image_imp_jpg( ).
DATA(lo_image_png) = NEW lcl_image_imp_png( ).
lo_image_windows->set_image_implementor( lo_image_jpg ).
lo_image_windows->output_image( ).
lo_image_windows->set_image_implementor( lo_image_png ).
lo_image_windows->output_image( ).
lo_image_linux->set_image_implementor( lo_image_jpg ).
lo_image_linux->output_image( ).
lo_image_linux->set_image_implementor( lo_image_png ).
lo_image_linux->output_image( ).
运行结果:
可以看到,由于独立了“操作系统”与“图片显示”两个维度,使得两种维度可以独立变化和组合。
以上,是本篇对桥接模式的总结,欢迎分享、留言。😉
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!