设计模式之模板方法模式

原创
2019/08/22 12:00
阅读数 73

简介

模板方法模式(Template Method Pattern)隶属于设计模式中的行为型模式,与策略模式一样,是在平常编码过程最常用的模式之一,理念也很简单明确:老大制定流程,具体执行由小弟接手。

模式定义

模版方法模式:在一个方法中定义了一个算法的骨架或者步骤,而将一些步骤延迟到子类中去实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某一些步骤。

模板方法模式也是关于算法的,不过模板方法侧重与定义算法的骨架,即算法如何做的流程,而将每一个具体步骤的实现决定权交给子类。

角色

模板方法模式包含三种角色:使用方、抽象模板父类,具体子类实现。

模板方法只是使用了继承和多态,模板在定义操作步骤时实际上也已经将每一个步骤要遵守的规约也定好了,因此具体子类在重写父类方法要注意符合里氏替换原则。

模式说明

我们以早上从起床到上班为例,起床,刷牙,整理书包,坐交通工具到公司,这是一个很常见的流程,至于用什么牙膏,坐地铁还是公交车都不是流程所关心的,细节由子类来补充。这里我们刷牙有一个默认方法,交通方法交由子类决定。

public abstract class GotoWork {
    public final void go() {
        wakeup();
        brush();
        arrage();
        takeVehicle();
    }
    
    protected void wakeup() {
        System.out.println("open your eyes!");
    }
    
    protected abstract void brush() {
        System.out.println("brush your teeth");
    }
    protected abstract void arrage() {
        System.out.prinln("arrage bed and bag");
    }
    protected abstract void takeVehicle();
}

某一天我们坐公交车去到了公司

public class BusGotoWork {
    @Override
    public void takeVehicle() {
        System.out.println("take a bus");
    }
}

也可以坐班车:

public class ScheduledBusGotoWork {
    @Override
    public void takeVehicle() {
        System.out.println("take a scheduledbus");
    }
}

使用直接使用父类提供的对外接口,然后就会走完所有流程

public class Client {
    public static void main(String[] args) {
        // 坐班车
        GotoWork gotoWork = new ScheduledBusGotoWork();
        gotoWork.go();
        
        // 坐公交车
        GotoWork gotoWork1 = new BusGotoWork();
        gotoWork1.go();
    }
}

与其他设计模式配合使用

  1. 模板方法也是用于描述算法的,会有多个不同的算法实现,因此可以和创建型模式比如简单工厂方法模式、单例模式等配合使用甚佳。
  2. 桥接模式,上面的例子中刷牙和坐车这两个场景会有很多实现,直接在子类中写会显得臃肿且职责不清,可以将这两种抽离出来单独做成策略,然后桥接过来。
  3. 观察者模式,模版方法在某个事件发生后可以触发事件,由观察者来处理对应事件。
  4. 混合使用,具体场景具体分析

使用场景

适合于流程固定而具体处理步骤不同的场景。日常使用也很常见。

具体例子:

  1. Spring 的 AbstractApplicationContextrefresh 方法
  2. Java 中的 AbstractList 中的 addAll 等方法

优点

  • 封装流程,便于统一处理
  • 流程复用,可以减少子类代码量
  • 符合开闭原则,新增子类方便

缺点

  • 子类需要了解父类提供的可覆盖的方法,并且要小心遵守规约
  • 类增多
  • 流程追踪复杂,在子类和父类之间跳来跳去,加大寻找问题难度

最佳实践

流程中定义钩子方法,可由子类自由决定是否需要覆盖

上面的例子中只有两类方法:抽象方法和具体方法,模板模式中还存在第三种类型方法:钩子方法。

钩子方法是定义在父类中的空方法,这个方法并不是流程必须的,而是提供给子类一个处理的时机,便于子类自行定义,执行一些子类特定的操作。这也是为了不破坏父类流程的封装性。

在Java HashMap 中可以看到三个钩子方法

    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

流程方法可定义为 final

如果某个流程是确定的,不能让子类擅自修改,那么可以将模板方法定义成 final的,防止人为错误。

一个反面例子是Java 的 ClassLoader 提供了类的加载模板, 即检查未加载过 ---> 父类classloader 未加载到 ----> 子类加载,但却没有将方法设成 final 的,其实子类是不应该破坏类加载的双亲委派模型的。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部