ABAP设计模式之---“组合模式(Composite Pattern)”

1.目的

将对象组合成树形结构,以表示“部分-整体”的层次结构。

组合模式使得用户对单个对象和组合对象的使用具有"一致性"。

2.解读

类比:

  • 在现实生活中,我们会遇到很多的树状结构。例如,公司的组织架构(总公司-分公司),文件系统的层级结构,目录的层级结构,以及页面控件的层级关系等等。
  • 组合模式,就是一种利用面向对象的方式,来处理“部分-整体”关系的设计模式。

解析:

  • 组合模式中包含两种类型的对象,复杂对象(e.g. 文件夹)和原子对象(e.g. 文件)。复杂对象中可进一步包含子元素,而原子对象是最小单元,无任何子元素。
  • 使用复杂对象和原子对象,可以让类与类之间构建出“层级关系”
  • 无论是复杂对象或是原子对象,共同实现相同的接口。因此对于客户端而言,任何一个独立的对象都有“一致性”的对外行为

类图:

组合模式的主要特点在于,对于Composite对象,其会包含一个 mt_list[ ] 的属性,用来进一步存储子元素。子元素的增加和删除通过Add( )和Remove( )这些方法行为来实现。

方法Operation()是对象的主要行为,依具体的用例而设定,例如它可以是对某一个文件对象计算大小、扫描文件的存储情况、查杀病毒等等。

下图展示了“透明方式”的组合模式设计类图。
“安全方式”的组合模式设计类图
各种角色:

  1. Component: 目标对象的抽象接口(抽象类),定义所有元素的公共行为(确保复杂对象或是原子对象行为的“一致性”)
  2. Composite: 枝节点(复合类),定义了枝节点的行为,并作为container,用来存储子元素。在Composite中实现与子元素有关的操作,例如Add( )和Remove( )
  3. Leaf: 叶节点(原子类),不包含任何子节点

要点:

  1. Component接口设计:在架构设计阶段,设计者需要根据具体的用例,决定将哪些操作定义在公共接口中,哪些操作仅定义在复合类中。
  2. 设计Component接口的“透明方式”与“安全方式” :这个其实说的是如何处理复合类(composite)对于子元素(leaf)的操作行为,复合类需要有能力增加Add或删除Remove子元素,而这些操作对于叶子节点(Leaf)来说是无意义的,因为叶子节点的原子性,其不再包含任何子元素。
  3. 透明方式”中,这些Add(), Remove()等对于叶子节点的操作行为,全部统一定义在Component的接口中,这样的好处是,对于子叶节点和枝节点,其对于外界的行为是“一致的”,但这种行为对于叶子节点本身而言是无意义的,因为通常会在对应的叶子节点的实现类中,对于Add()和Remove()操作会抛出异常,确保其不会被使用
  4. 安全方式”中,在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前后,层级结构的对比。
在这里插入图片描述

以上,是本篇对组合模式的总结,欢迎分享、留言。😉

本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十年铸器

给作者赏杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值