模板方法模式&lambda重构模板方法模式

一、概念以及背景

模板方法模式(Template Method Pattern):定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

简单来说,当你频繁地需要执行某些操作,这其中的操作有共性,也有差异性的地方,我们可以用模板方法把共性的操作抽取出来,即定义一个操作中算法的“框架”,把差异性的步骤延迟到子类中,即让子类来实现差异化的步骤,让我们看下文的例子,从例子中体会更容易理解。

文章涉及的代码在github上,点击 链接 可查看源码。

本文会用两种方式来实现模板方法模式,第二种是用lambda表达式的方式来实现,参数行为化的方式实现,最后会给出实际开发中的一点总结,使开发中更加简化代码。

笔者想了一段时间,想不出什么更好的例子来作为引入场景,就用“组装汽车”作为场景,或许这个场景离实际开发有些距离,不过来理解模板方法还不错。

本文所使用的场景如下:组装汽车,首先需要制作四个轮子,其次制作两个镜子,然后造车身,最后给车子上油漆,涂上喜欢的颜色,其中我们假设造轮子、镜子、车身都是共性的地方,即步骤都一样,只有最后涂车子颜色是差异化的地方,即不一样的地方。

二、造车子

  • Car类,有轮子wheel、镜子mirror、车身carBody、颜色color四个属性(此处省略set、get、toString等方法)。
public class Car {
    /**
     * 车轮子
     */
    private Integer wheel;

    /**
     * 镜子
     */
    private Integer mirror;

    /**
     * 车身
     */
    private Boolean carBody;

    /**
     * 车颜色
     */
    private String color;

}
  • ColorEnuum车颜色枚举,假设车有白色、黑色、红色。
public enum ColorEnuum {

    WHITE("白色"),BLACK("黑色"),RED("红色");

    String color;

    ColorEnuum(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}
  • MakeCarUtils,造车util类,前面我们提到,制作轮子、镜子、车身是共性的地方,造车的步骤都会经过这三个一样的步骤,故写成utils,来提供造车时候调用。
public class MakeCarUtils {

    public static void makeWheel(Car car) {
        System.out.println(car.hashCode()+"-正在制作轮子-1");
        car.setWheel(4);
        System.out.println(car.hashCode()+"-轮子制造完成-2");
    }

    public static void makeMirror(Car car) {
        System.out.println(car.hashCode()+"-正在制作后视镜-3");
        car.setMirror(2);
        System.out.println(car.hashCode()+"-后视镜制造完成-4");
    }

    public static void makeCarBody(Car car) {
        System.out.println(car.hashCode()+"-正在制作车身-5");
        car.setCarBody(true);
        System.out.println(car.hashCode()+"-车身制造完成-6");
    }
}

开始我们的造车,我们会发现,每次造车,都需要频繁地调用MakeCarUtils的makeWheel、makeMirror、makeCarBody这三个步骤,而且都是一样的步骤,当我们造十几二十辆车的时候,代码会非常冗长,而且也是没必要的,除了差异化的地方,给车涂上喜欢的颜色,且看下文,用模板方法来简化我们的代码。

public class TemplateMain {

    public static void main(String[] args) {
        Car car1 = new Car();
        MakeCarUtils.makeWheel(car1);
        MakeCarUtils.makeMirror(car1);
        MakeCarUtils.makeCarBody(car1);
        car1.setColor(ColorEnuum.BLACK.getColor());
        System.out.println(car1);

        Car car2 = new Car();
        MakeCarUtils.makeWheel(car2);
        MakeCarUtils.makeMirror(car2);
        MakeCarUtils.makeCarBody(car2);
        car2.setColor(ColorEnuum.WHITE.getColor());
        System.out.println(car2);

        Car car3 = new Car();
        MakeCarUtils.makeWheel(car3);
        MakeCarUtils.makeMirror(car3);
        MakeCarUtils.makeCarBody(car3);
        car3.setColor(ColorEnuum.RED.getColor());
        System.out.println(car3);
    }
}
  • 控制台输出:
1173230247-正在制作轮子-1
1173230247-轮子制造完成-2
1173230247-正在制作后视镜-3
1173230247-后视镜制造完成-4
1173230247-正在制作车身-5
1173230247-车身制造完成-6
Car{wheel=4, mirror=2, carBody=true, color='黑色'}
856419764-正在制作轮子-1
856419764-轮子制造完成-2
856419764-正在制作后视镜-3
856419764-后视镜制造完成-4
856419764-正在制作车身-5
856419764-车身制造完成-6
Car{wheel=4, mirror=2, carBody=true, color='白色'}
621009875-正在制作轮子-1
621009875-轮子制造完成-2
621009875-正在制作后视镜-3
621009875-后视镜制造完成-4
621009875-正在制作车身-5
621009875-车身制造完成-6
Car{wheel=4, mirror=2, carBody=true, color='红色'}

三、模板方法模式

模板方法需要一个抽象类,提供一个抽象方法,来提供让子类对差异化的步骤进行不同的实现,而把共性的步骤抽取出来作为一个方法,这样一来,共性的步骤有相同的方法可以调用,差异化的地方子类进行实现,我们也可以实现着来调用。

模板方法uml图如下:makeCar是共性的步骤,即造轮子、镜子、车身,makeColor是差异化的步骤,即给车子涂上我们喜欢的颜色,这里有三个子类继承抽象父类CarTemplate,即制造黑色车子、白色车子、红色车子的子类。


uml
  • CarTemplate抽象父类,makeCar方法是共性的步骤,makeColor让子类提供不同的实现。
public abstract class CarTemplate {

    final void makeCar(Car car) {
        System.out.println(car.hashCode()+"-正在制作轮子-1");
        car.setWheel(4);
        System.out.println(car.hashCode()+"-轮子制造完成-2");

        System.out.println(car.hashCode()+"-正在制作后视镜-3");
        car.setMirror(2);
        System.out.println(car.hashCode()+"-后视镜制造完成-4");

        System.out.println(car.hashCode()+"-正在制作车身-5");
        car.setCarBody(true);
        System.out.println(car.hashCode()+"-车身制造完成-6");

        makeColor(car);
    }

    abstract void makeColor(Car car);
}
  • MakeBlackCar类,造黑色车子
public class MakeBlackCar extends CarTemplate {
    @Override
    void makeColor(Car car) {
        car.setColor(ColorEnuum.BLACK.getColor());
    }
}
  • MakeRedCar类,造红色车子
public class MakeRedCar extends CarTemplate{
    @Override
    void makeColor(Car car) {
        car.setColor(ColorEnuum.RED.getColor());
    }
}
  • MakeWhiteCar类,造白色车子
public class MakeWhiteCar extends CarTemplate{
    @Override
    void makeColor(Car car) {
        car.setColor(ColorEnuum.WHITE.getColor());
    }
}

开始我们的造车,当我们需要造不同颜色的车的时候,只需要用不同的造车模板,即可实现造车,比如用白色造车模板MakeWhiteCar,即可造很多白色的车子,而无需再重复地在代码中调用util的makeWheel、makeMirror、makeCarBody这三个步骤。

public class TemplateMain {

    public static void main(String[] args) {
        Car wantWhiteCar = new Car();
        Car wantBlackCar = new Car();
        Car wantRedCar = new Car();
        CarTemplate templateWhiteCar = new MakeWhiteCar();
        CarTemplate templateBlackCar = new MakeBlackCar();
        CarTemplate templateRedCar = new MakeRedCar();
        templateWhiteCar.makeCar(wantWhiteCar);
        templateBlackCar.makeCar(wantBlackCar);
        templateRedCar.makeCar(wantRedCar);
        System.out.println(wantWhiteCar);
        System.out.println(wantBlackCar);
        System.out.println(wantRedCar);
    }
}

当然,代码中需要用子类去实现父类,这样一下子就多出4个类,实际上子类也只是实现了差异化的步骤,我们可以结合Java8,把行为参数化,即把差异化的步骤当作参数来传递,即可不用写三个子类去实现抽象父类的抽象方法,且看下文。

四、lambda重构模板方法模式

  • LambdaCarTemplate类,该模板类只有一个makeCar方法,方法第二个参数是一个函数式接口Consumer<T>,Java8提供了很多有用的函数式接口给我们用,基本上是满足我们的开发需求的,当然我们也可以自己声明函数式接口来满足我们的需求。
public class LambdaCarTemplate {

    public static void makeCar(Car car, Consumer<Car> consumer) {
        System.out.println(car.hashCode()+"-正在制作轮子-1");
        car.setWheel(4);
        System.out.println(car.hashCode()+"-轮子制造完成-2");
        System.out.println(car.hashCode()+"-正在制作后视镜-3");
        car.setMirror(2);
        System.out.println(car.hashCode()+"-后视镜制造完成-4");
        System.out.println(car.hashCode()+"-正在制作车身-5");
        car.setCarBody(true);
        System.out.println(car.hashCode()+"-车身制造完成-6");
        consumer.accept(car);
    }
}
  • Consumer<T>函数式接口,入参是泛型T,无返回类型,即如接口名称描述,该接口是一个消费接口。
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    }
}

开始我们的造车,可以看到,这次简化了很多代码,也不用写三个子类去继承抽象父类,实现抽象父类的抽象方法,当然,这里调用造车模板LambdaCarTemplate的makeCar,方法第二个参数我们也可以封装成方法,用方法引用即可不用每次写。

public class TemplateMain {

    public static void main(String[] args) {

        Car whiteCar = new Car();
        Car blackCar = new Car();
        LambdaCarTemplate.makeCar(whiteCar, (Car car) -> car.setColor(ColorEnuum.WHITE.getColor()));
        LambdaCarTemplate.makeCar(blackCar, (Car car) -> car.setColor(ColorEnuum.BLACK.getColor()));
        System.out.println(whiteCar);
        System.out.println(blackCar);

    }
}
  • LambdaCarTemplate类,增加三个方法,把涂颜色的实现封装成方法来引用。
public class LambdaCarTemplate {

    public static void makeWhiteCar(Car car) {
        car.setColor(ColorEnuum.WHITE.getColor());
    }

    public static void makeRedCar(Car car) {
        car.setColor(ColorEnuum.RED.getColor());
    }

    public static void makeBlackCar(Car car) {
        car.setColor(ColorEnuum.BLACK.getColor());
    }
}
  • TemplateMain类,这样每次就无需在造车的时候在makeCar方法写实现。
public class TemplateMain {

    public static void main(String[] args) {
        Car makeWhite = new Car();
        Car makeRed = new Car();
        LambdaCarTemplate.makeCar(makeWhite, LambdaCarTemplate::makeWhiteCar);
        LambdaCarTemplate.makeCar(makeRed, LambdaCarTemplate::makeRedCar);
        System.out.println(makeWhite);
        System.out.println(makeRed);
    }
}

五、开发中的一些思考

实际开发中,我们可以借助模板方法和Lambda行为参数化,来大大简化我们的代码,只需要把共性的代码抽取出来,放到一个方法里面,然后该方法提供一个函数式接口,让我们在调用该方法的时候,用Lambda来实现差异化的地方,即可简化我们的代码。

  • 最后附上Java8提供的内置函数式接口,比较常用的几个接口:


    内置函数式接口

本文如有不足之处,请指正或者提出好的建议。◕‿◕。谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容