模板方法模式&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,即製造黑色車子、白色車子、紅色車子的子類。


  • 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提供的內置函數式接口,比較常用的幾個接口:


本文如有不足之處,請指正或者提出好的建議。◕‿◕。謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章