1.目的
定义一个操作中算法的骨架,将一些步骤延迟到子类中。
模板方法模式使得子类可以在不改变算法结构的前提下,重写算法的某些步骤。
2.解读
类比:
- 我们的日常生活中,可以遇到很多的模板。例如,编写简历时,我们会使用一些简历模板;填写表格时,会有一个填表的参照模板;编写文章时,根据叙事方式的不同,也可参照相应的写作模板。可以说,“模板”其实充斥在我们日常生活中。
- 想一想,模板的好处是什么?模板可以为使用者提供一个参照对象,并将某些方面固化下来,形成“套路”。这种方式简化了操作步骤,并提升了效率。在面向对象的程序世界里,我们将这个“模板”抽象成了一种设计模式,也即“模板方法模式”。
解析:
- 模板方法模式其实是一种基于“继承”的代码复用技术,它属于行为型这一类的设计模式
- 对于“继承”,可能大多数同学对它是又爱又恨。问什么爱它呢?因为它能很方便地实现代码的复用。为什么恨它呢?因为继承的入侵性,父类的改动会直接影响到子类,破坏了“开放-封闭原则”。同时“里式替换原则”和“合成/聚合复用原则”都在提醒我们继承带来的风险和副作用。其实,这些原则说的都是对的,关键是我们如果合理地应用这些原则,“继承”不是程序设计的禁令,关键在于平衡各个原则之间的关系,合理地使用“继承”。
- 模板方法模式,其实就是很好地平衡了这几个原则的一个应用场景
万事万物没有绝对的好与坏,关键在于使用的人、使用的方式。把合适的人或物,放到/应用到合适的位置/场景,才是真正的高手。
类图:
各种角色:
- Abstract Class: 提供模板的抽象类,定义并实现了一个模板方法。这个模板方法给出了一个逻辑框架,而具体逻辑的组成步骤,推迟到子类中实现
- Concrete Class: 模板的实现类,实现模板类的一个或多个抽象方法
要点:
- 模板类定义的算法的框架,对于不同的场景,可以继承模板类,并在子类中实现定制化的逻辑。 模板类通常应定义为抽象类,不允许实例化,仅其具体的实现子类方可实例化。也即,应将模板仅是一个通用参照对象,具体的实际对象的实例才有具体的含义
- 模板类中的方法,应有合理的限定。例如模板方法应当不允许重写,子类仅可重写具体的步骤实现方法;在模板方法中使用Hook Method(钩子方法),可以让子类反向控制模板方法的执行逻辑
- 在使用时,应当尽量限定可重写方法的数量,因为随着数量的增加,代码的复杂度和子类的实现复杂度都会随之升高。同时,为了避免“继承”带来的代码侵入性,对于留给子类实现的方法应当尽可能定义为“抽象方法”,父类不要给出实现,对于钩子方法和特殊用的方法除外
- 良好的命名规范,可以使得模板方法模式的设计变得更加清楚。例如对于留给子类实现的方法可以用do_someting( ), 对于钩子方法可以用Is_xxx( ), Is_pre_xxx( ), Is_post_xxx( )等命名
下图展示了,加入Hook Method后,模板方法的执行流程图。
设计模式对比:
- 模板方法适用于一些复杂算法的分割,固定的部分设计为模板类,而细节实现留个子类。 当模板类中变化的部分过多时,应当考虑使用“桥接模式”替代,因为过多的变化意味着更多可能子类的实现,“桥接模式”更适合处理这种多维度变化的问题
- 模板方法模式同策略模式的异同:模板方法模式是通过“继承”实现的代码复用,策略模式是通过“接口”实现算法家族的定义。对客户端而言,策略模式使得客户端可独立于具体算法的变化;而模板方法模式,则要求客户端应该直接使用具体的实现子类
3. 示例代码
下面给出一个利用模板方法模式,制作三明治的例子。
三明治制作的步骤是固定的,需要加面包片,奶油,蔬菜等等,但对于不同类型的三明治,其具体的搭配可以是不同的。
在使用模板方式,应注意对于各个方法的关键字限定。
REPORT ztemplate_method_pattern.
CLASS lcl_template_sandwich DEFINITION ABSTRACT CREATE PROTECTED.
PUBLIC SECTION.
METHODS prepare_sandwich FINAL. " do not allow to redefine
PROTECTED SECTION.
METHODS add_butter. " can be overwritten
METHODS add_extra ABSTRACT. " needs to be redefined
METHODS add_veggetables ABSTRACT. " needs to be redefined
PRIVATE SECTION.
METHODS slice_bread.
ENDCLASS.
CLASS lcl_template_sandwich IMPLEMENTATION.
METHOD add_butter.
WRITE: / 'Add thin layer of butter'.
ENDMETHOD.
METHOD prepare_sandwich.
slice_bread( ).
add_butter( ).
add_extra( ).
add_veggetables( ).
ENDMETHOD.
METHOD slice_bread.
WRITE:/ 'Slice bread'.
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_cheese_sandwich DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_template_sandwich.
PROTECTED SECTION.
METHODS:
add_butter REDEFINITION,
add_extra REDEFINITION,
add_veggetables REDEFINITION.
ENDCLASS.
CLASS lcl_cheese_sandwich IMPLEMENTATION.
METHOD add_butter.
WRITE: / 'Add thick layer of butter'.
ENDMETHOD.
METHOD add_extra.
WRITE: / 'Add slices of cheese'.
ENDMETHOD.
METHOD add_veggetables.
WRITE: / 'Add tomato slices'.
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_ham_sandwich DEFINITION FINAL CREATE PUBLIC
INHERITING FROM lcl_template_sandwich.
PROTECTED SECTION.
METHODS: add_extra REDEFINITION,
add_veggetables REDEFINITION.
ENDCLASS.
CLASS lcl_ham_sandwich IMPLEMENTATION.
METHOD add_extra.
WRITE: / 'Add slices of ham'.
ENDMETHOD.
METHOD add_veggetables.
WRITE: / 'Add salad leaves'.
WRITE: / 'Add onions'.
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
DATA lt_sandwich TYPE TABLE OF REF TO lcl_template_sandwich.
lt_sandwich = VALUE #( ( NEW lcl_cheese_sandwich( ) )
( NEW lcl_ham_sandwich( ) ) ).
LOOP AT lt_sandwich INTO DATA(lo_sandwich).
WRITE: / '...This is sandwich :' && sy-tabix && '...'.
lo_sandwich->prepare_sandwich( ).
ENDLOOP.
在上例中,我们利用模板三明治,制作了奶酪三明治和火腿三明治。程序运行结果如下:
下面给出一个简单的示例,展示了如何使用Hook Method钩子方法控制模板方法中的执行逻辑:
CLASS lcl_template_sandwich DEFINITION ABSTRACT CREATE PROTECTED.
PUBLIC SECTION.
METHODS prepare_sandwich FINAL. " do not allow to redefine
PROTECTED SECTION.
METHODS add_butter. " can be overwritten
METHODS is_extra_required RETURNING VALUE(rv_yes) TYPE abap_bool. " hook method
METHODS add_extra ABSTRACT. " needs to be redefined
METHODS add_veggetables ABSTRACT. " needs to be redefined
PRIVATE SECTION.
METHODS slice_bread.
ENDCLASS.
CLASS lcl_template_sandwich IMPLEMENTATION.
METHOD add_butter.
WRITE: / 'Add thin layer of butter'.
ENDMETHOD.
METHOD prepare_sandwich.
slice_bread( ).
add_butter( ).
IF is_extra_required( ). " hook to control whether a specific step needs to be executed
add_extra( ).
ENDIF.
add_veggetables( ).
ENDMETHOD.
METHOD slice_bread.
WRITE:/ 'Slice bread'.
ENDMETHOD.
METHOD is_extra_required.
rv_yes = abap_false.
ENDMETHOD.
ENDCLASS.
此例中的方法 :
* METHODS is_extra_required RETURNING VALUE(rv_yes) TYPE abap_bool. *
就是一个hook method,它使得子类可以有选择地实现模板类中定义的步骤方法,并通过返回值,控制模板类中算法的执行。
.
.
.
以上,是本篇对模板方法模式的总结,欢迎分享、留言。😉
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!
更多推荐
ABAP设计模式之---“模板方法模式(Template Method Pattern)”
发布评论