1.目的
将对象组合成树形结构,以表示“部分-整体”的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有"一致性"。
2.解读
类比:
- 在现实生活中,我们会遇到很多的树状结构。例如,公司的组织架构(总公司-分公司),文件系统的层级结构,目录的层级结构,以及页面控件的层级关系等等。
- 组合模式,就是一种利用面向对象的方式,来处理“部分-整体”关系的设计模式。
解析:
- 组合模式中包含两种类型的对象,复杂对象(e.g. 文件夹)和原子对象(e.g. 文件)。复杂对象中可进一步包含子元素,而原子对象是最小单元,无任何子元素。
- 使用复杂对象和原子对象,可以让类与类之间构建出“层级关系”
- 无论是复杂对象或是原子对象,共同实现相同的接口。因此对于客户端而言,任何一个独立的对象都有“一致性”的对外行为
类图:
组合模式的主要特点在于,对于Composite对象,其会包含一个 mt_list[ ] 的属性,用来进一步存储子元素。子元素的增加和删除通过Add( )和Remove( )这些方法行为来实现。
方法Operation()是对象的主要行为,依具体的用例而设定,例如它可以是对某一个文件对象计算大小、扫描文件的存储情况、查杀病毒等等。
下图展示了“透明方式”的组合模式设计类图。
各种角色:
- Component: 目标对象的抽象接口(抽象类),定义所有元素的公共行为(确保复杂对象或是原子对象行为的“一致性”)
- Composite: 枝节点(复合类),定义了枝节点的行为,并作为container,用来存储子元素。在Composite中实现与子元素有关的操作,例如Add( )和Remove( )
- Leaf: 叶节点(原子类),不包含任何子节点
要点:
- Component接口设计:在架构设计阶段,设计者需要根据具体的用例,决定将哪些操作定义在公共接口中,哪些操作仅定义在复合类中。
- 设计Component接口的“透明方式”与“安全方式” :这个其实说的是如何处理复合类(composite)对于子元素(leaf)的操作行为,复合类需要有能力增加Add或删除Remove子元素,而这些操作对于叶子节点(Leaf)来说是无意义的,因为叶子节点的原子性,其不再包含任何子元素。
- “透明方式”中,这些Add(), Remove()等对于叶子节点的操作行为,全部统一定义在Component的接口中,这样的好处是,对于子叶节点和枝节点,其对于外界的行为是“一致的”,但这种行为对于叶子节点本身而言是无意义的,因为通常会在对应的叶子节点的实现类中,对于Add()和Remove()操作会抛出异常,确保其不会被使用
- “安全方式”中,在Component中不会定义Add()和Remove()操作,他们只会被定义在Composite类中,这样就避免了叶子节点中的冗余方法。但这个方式也会带来一些问题,因为叶子节点和枝节点接口的不一致,client端在使用时,需要判断对象的实例化类型,也即,判断是叶子节点or枝节点。一定程度上,给使用者带来不便。
下图展示了“安全方式”的组合模式设计类图。
体现的设计原则:
- 开放封闭原则 :在组合模式中,在树状结构中增加新的子元素很方便,无须对现有类进行任何修改,符合“开闭原则”
3.举例
在此示例中,用最简单的代码,构建出一个类与类之间的层级结构,并实现显示功能。
REPORT zcomposite_pattern.
CLASS lcl_component DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS add ABSTRACT
IMPORTING
io_component TYPE REF TO lcl_component .
METHODS remove ABSTRACT
IMPORTING
io_component TYPE REF TO lcl_component.
METHODS display ABSTRACT
IMPORTING
iv_depth TYPE i.
METHODS constructor
IMPORTING iv_name TYPE string.
PROTECTED SECTION.
DATA mv_name TYPE string.
METHODS get_name
RETURNING
VALUE(rv_name) TYPE string.
ENDCLASS.
CLASS lcl_component IMPLEMENTATION.
METHOD get_name.
rv_name = mv_name.
ENDMETHOD.
METHOD constructor.
mv_name = iv_name.
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_leaf DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_component.
PUBLIC SECTION.
METHODS:
add REDEFINITION,
remove REDEFINITION,
display REDEFINITION.
ENDCLASS.
CLASS lcl_leaf IMPLEMENTATION.
METHOD add.
WRITE / 'Cannot add to a leaf'.
ENDMETHOD.
METHOD remove.
WRITE / 'Cannot remove from a leaf'.
ENDMETHOD.
METHOD display.
DATA lv_placeholder TYPE string.
DO iv_depth TIMES.
lv_placeholder = lv_placeholder && '.'.
ENDDO.
WRITE: / lv_placeholder && mv_name.
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_composite DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_component.
PUBLIC SECTION.
METHODS:
add REDEFINITION,
remove REDEFINITION,
display REDEFINITION.
PRIVATE SECTION.
DATA lt_list TYPE STANDARD TABLE OF REF TO lcl_component WITH EMPTY KEY.
ENDCLASS.
CLASS lcl_composite IMPLEMENTATION.
METHOD add.
APPEND io_component TO me->lt_list.
ENDMETHOD.
METHOD display.
DATA lv_placeholder TYPE string.
DO iv_depth TIMES.
lv_placeholder = lv_placeholder && '.'.
ENDDO.
WRITE: / lv_placeholder && mv_name.
LOOP AT me->lt_list INTO DATA(lo_comp).
lo_comp->display( iv_depth + 1 ).
ENDLOOP.
ENDMETHOD.
METHOD remove.
LOOP AT me->lt_list INTO DATA(lo_comp).
IF lo_comp->get_name( ) = io_component->get_name( ).
DATA(lv_index) = sy-tabix.
EXIT.
ENDIF.
ENDLOOP.
DELETE me->lt_list INDEX lv_index.
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
DATA(lo_root) = NEW lcl_composite( 'Root' ).
lo_root->add( NEW lcl_leaf( 'Leaf A' ) ).
lo_root->add( NEW lcl_leaf( 'Leaf B' ) ).
DATA(lo_comp_x) = NEW lcl_composite( 'Level X' ).
lo_comp_x->add( NEW lcl_leaf( 'Leaf XA' ) ).
lo_comp_x->add( NEW lcl_leaf( 'Leaf XB' ) ).
lo_root->add( lo_comp_x ).
DATA(lo_comp_y) = NEW lcl_composite( 'Level Y' ).
lo_comp_y->add( NEW lcl_leaf( 'Leaf YA' ) ).
lo_comp_y->add( NEW lcl_leaf( 'Leaf YB' ) ).
DATA(lo_comp_y2) = NEW lcl_composite( 'Level Y2' ).
lo_comp_y2->add( NEW lcl_leaf( 'Leaf Y2A' ) ).
lo_comp_y->add( lo_comp_y2 ).
lo_root->add( lo_comp_y ).
lo_root->display( 1 ).
WRITE / '------'.
lo_comp_y->remove( lo_comp_y2 ).
lo_root->display( 1 ).
运行结果:
在此例中,一个点代表一个层级,我们可以看到由root出发,在此节点下所有的层级结构。
分割线上下两部分,分别输出了在删除节点Y2前后,层级结构的对比。
以上,是本篇对组合模式的总结,欢迎分享、留言。😉
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!