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

Java基础

浏览数:54

2020-6-14

一、概念以及背景

模板方法模式(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,即制造黑色车子、白色车子、红色车子的子类。

  • 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 Consumer<Car> makeBlackCar() {
        return (Car car) -> car.setColor(ColorEnuum.BLACK.getColor());
    }

    public static Consumer<Car> makeWhiteCar() {
        return (Car car) -> car.setColor(ColorEnuum.WHITE.getColor());
    }

    public static Consumer<Car> makeRedCar() {
        return (Car car) -> car.setColor(ColorEnuum.RED.getColor());
    }
}
  • TemplateMain类,这样每次就无需在造车的时候在makeCar方法写实现。
public class TemplateMain {

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

五、开发中的一些思考

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

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

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

作者:Fiuty