欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

面向对象设计模式学习总结之模板模式(Template Method)

程序员文章站 2022-06-08 19:06:30
...

2020-01-16-模板模式(Template Method ):

一、.把我能想到的写下来:

以过年回家为例,游子需要完成以下步骤:
1.买票buy ticket
2.搭乘相应的交通工具(长途汽车/火车/飞机)回家 travel by …
3.回家过年 happy new year!
不同的游子选择的交通工具可能不同,然而1与3的步骤总是要有的,而且顺序也一定是按照“1.2.3”的,为了避免代码的冗余性,可以抽象出一个父类abstract class HappyPeople。其中有方法celebrateSpringFestival(),该方法需要保证“1.2.3”的顺序,即按顺序调用1.2.3对应的方法,而1.buy ticket需要是final修饰的,因为其对于任何游子都是一致的功能,就是买票,不允许被子类覆写,同理,3.happy new year也是final修饰的,只有2 travel by …是abstract修饰,由子类各自去覆写相应的交通工具方法。
画出的类图如下所示:(终于拥有了visio,以后要养成画类图的习惯了。)
面向对象设计模式学习总结之模板模式(Template Method)
抽象类是不能被实例化的,那么不同的游子就会去new相应的类,由父类happyPeople引用着,那么不同的游子调用方法时就会因为多态的特点,调用相应的交通工具方法。
模板模式的优点:减少代码冗余性、提高可维护性、(只改父类代码,或者相应子类覆写的方法)、好莱坞原则“你不要主动联系我,我会主动联系你的”/子类绝对不可主动调用父类方法,父类会去调用子类的。

二、根据书补充:

模板方法模式(Template Method)

有人需要坐火车回家,有人需要坐飞机回家,而有人坐大巴回家就可以。
但是不管乘坐哪种交通工具回家,都得先买票,然后才能回家团聚。

  public class HappyPeople{
//Buy ticket...
System.out.println("Buy ticket...");

//Traveling by train...
    System.out.println("Traveling by train");

    //Celebrating Chinese new year...
    System.out.println("Happy Chinese New Year!");}}
     

新建一个PassengerByCoach类——坐长途汽车回家过年。
由于买票与在家庆祝的逻辑与HappyPeople中的相同,只是把乘坐的交通工具改成coach

public class PassengerByCoach {
    public void celebrateSpringFestival()
    {
//Buy ticket...
        System.out.println("Buy ticket...");

//Traveling by coach...
        System.out.println("Traveling by coach");

        //Celebrating Chinese new year...
        System.out.println("Happy Chinese New Year!");
    }
}

接着,我们同样使用复制+粘贴(Copy&Paste)方式编写了类PassengerByAir来实现表示坐飞机回家的
那类人的需求。


public class PasserByAir {

    //Buy ticket...
        System.out.println("Buy ticket...");

//Traveling by air...
        System.out.println("Traveling by air");

    //Celebrating Chinese new year...
        System.out.println("Happy Chinese New Year!");
}
}

复制+粘贴看起来非常实用,但是没过几周,就会慢慢发现问题的不妙,
这几个类的代码开始变得难以维护

  • 如果买票的逻辑有所改变,我们需要分别修改这三个类,
    但有的时候,马虎的工程师不会在所有类上做相应的修改。
  • 而且,由于这些类的功能发生了变化,相应类的测试代码也要做改变,
    这样修改这些类的测试代码和修改这些类一样,出现了相同的重复修改的问题。
  • 随着交通工具的增多,势必需要开发更多类和测试类,这样维护就会变得越来越麻烦。

DRY(Don’t Repeat Yourself)原则 / DIE(Duplication is Evil)复制是魔鬼
OAOO(Once and Only Once) 仅此一次,避免代码重复,代码应该简洁。

为了重复使用代码,可以使用OOP(Object Oriented Programming)一大特征——继承。
既然PasserByCoach类和HappyPeople类订票和庆祝团圆的逻辑是相同的,
我们可以抽象出一个父类,把这些相同的逻辑写在父类中。
为父类定义subscribeTicket()方法和celebrate()方法,
这些方法实现了不变的部分——订票和庆祝团圆。
子类需要在travel()方法中实现各自的回家方式。
并且我们在父类中定义了celebrateSpringFestival()方法供客户对象调用。

public abstract class HappyPeople {
public void celebrateSpringFestival()
{
    subscribeTicket();
    travel();
    celebrate();
    hook();
}
//买票
protected final void subscribeTicket()
{System.out.println("buying ticket...");}
//交给子类各自覆写回家方式
protected abstract void travel();
protected final void celebrate()
{
System.out.println("Happy Chinese New Year!");
}

//可以有“默认不做事的方法”,称之为“钩子hook”
    //子类可以视情况而定要不要覆盖它们。
void hook()
{}


}

顺便复习一下abstract抽象类和protected final关键字:
摘自《Java核心技术卷1》:

抽象类
如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。
从某种角度上来说,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
除了抽象方法外,抽象类可以包含具体数据和具体方法。
抽象方法充当着占位的角色,它们的具体实现在子类中。
抽象类不能被实例化。

受保护访问protected:
有时候人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域,
需要将这些方法或域声明为protected

阻止继承:fianl类和方法

回顾代码,父类中的方法celebrateSpringFestival()是我们的一个模板方法,
它把回家分为三步:其中方法travel()是抽象部分,用于子类实现不同客户化逻辑。

给出

模板模式的定义:

Define the skeleton of an algorithm in an operation,
deferring some steps to subclasses.
Templete Method lets subclasses redefine certain steps of an algorithm without
changing the algorithm’s structure.
定义一个操作中的一个算法框架,
把一些步骤推迟到子类中去实现。
模板方法模式让子类不需要改变算法结构而重新定义特定的算法步骤。

也就是说模板方法定义了一系列算法步骤,子类可以去实现/覆盖其中某些步骤,
但不能改变这些步骤的执行顺序,模板方法有如下功能:
1.能够解决代码冗余问题。
2.把某些算法步骤延迟到子类,子类可以根据不同情况改变/实现这些方法,
而子类的新方法不会引起既有父类的功能变化。
3.易于扩展。
我们通过创建新类,实现可定制化的方法就可以扩展功能。
4.父类提供了算法框架,控制方法执行流程,而子类不能改变算法流程,
子类方法的调用由父类模板方法决定。
执行步骤的顺序有时候非常重要,我们在容器加载和初始化资源时,为避免子类执行错误的顺序,
经常使用该模式限定子类方法的调用次序。
5.父类可以把那些重要的、不允许改变的方法屏蔽掉,不让子类去覆写它们,比如声明成private或final即可。

模板模式的应用很广泛,但是过分使用模板方法往往会引起子类的泛滥。
比如,我们有如下需求:查询数据库里的记录:
1.首先需要得到数据库连接Connection对象。
2.然后创建Statement实例并执行相关的查询语句。
3.最后处理查询出来的结果并在整个执行过程中处理异常。
可以发现:1.2以及异常处理的逻辑对于每次查询来说都是相同的,
发生变化的部分主要是在对查询结果的处理上。
模板模式很适合处理这个问题,我们只需要抽象出这个处理查询结果的不同方法供不同的子类去延迟实现即可。
如果查询太多,就会导致需要创建很多的子类来处理这些查询结果,引起子类的泛滥。
为了解决该问题,通常我们结合回调(callback)来处理。

回调表示一段可执行逻辑的引用(或指针),
我们把该引用(或指针)传递到另外一段逻辑(或方法)里供这段逻辑适时调用。
回调在不同语言中有不同的实现,例如C语言里经常使用函数指针实现回调,在C#语言里使用代理(delegate)实现,
而在Java语言里使用匿名内部类实现回调。

客户对象使用SimpleJdbcQueryTemplate类的query(String queryString,ResultSetHandler rsHandler)
方法时,需要把回调作为第二个参数传递给这个方法,query()方法会在做完查询后执行回调的handle(Result rs)
方法处理查询,返回最后的处理结果。
类图如下所示:(然而我并没有搞懂回调的用法,以后遇到了再说吧.。)
面向对象设计模式学习总结之模板模式(Template Method)
另:
hook钩子是一种被声明在抽象类中的方法,
但只有空的或者默认的实现。

钩子的存在,可以让子类有能力对算法的不同点进行挂钩。
有了钩子,能够决定要不要覆盖方法,如果不提供自己的方法,抽象类会提供一个默认的实现。

相关标签: 总结 学习笔记