天天看點

使用Java8改造出來的模闆方法真的是yyds

我們在日常開發中,經常會遇到類似的場景:當要做一件事兒的時候,這件事兒的步驟是固定好的,但是每一個步驟的具體實作方式是不一定的。

通常,遇到這種情況,我們會把所有要做的事兒抽象到一個抽象類中,并在該類中定義一個模闆方法。這就是所謂的模闆方法模式。

以前的模闆方法

在我之前的一篇《

設計模式——模闆方法設計模式

》文章中舉過一個例子:

當我們去銀行的營業廳辦理業務需要以下步驟:1.取号、2.辦業務、3.評價。

三個步驟中取号和評價都是固定的流程,每個人要做的事兒都是一樣的。但是辦業務這個步驟根據每個人要辦的事情不同是以需要有不同的實作。

我們可以将整個辦業務這件事兒封裝成一個抽象類:

/**
 * 模闆方法設計模式的抽象類
 * @author hollis
 */
public abstract class AbstractBusinessHandler {
    /**
     * 模闆方法
     */
    public final void execute(){
        getNumber();
        handle();
        judge();
    }
    /**
     * 取号
     * @return
     */
    private void getNumber(){
        System.out.println("number-00" + RandomUtils.nextInt());
    }
    /**
     * 辦理業務
     */
    public abstract void handle(); //抽象的辦理業務方法,由子類實作
    /**
     * 評價
     */
    private void judge(){
        System.out.println("give a praised");
    }
}

           

我們在類中定義了一個execute類,這個類編排了getNumber、handle和judge三個方法。這就是一個模闆方法。

其中getNumber和judge都有通用的實作,隻有handle方法是個抽象的,需要子類根據實際要辦的業務的内容去重寫。

有了這個抽象類和模闆方法,當我們想要實作一個"存錢業務"的時候,隻需要繼承該AbstractBusinessHandeler并且重寫handle方法即可:

public class SaveMoneyHandler extends AbstractBusinessHandeler {
    @Override
    public void handle() {
        System.out.println("save 1000");
    }
}

           

這樣,我們在執行存錢的業務邏輯的時候,隻需要調用 SaveMoneyHandler的execute方法即可:

public static void main(String []args){
    SaveMoneyHandler saveMoneyHandler = new SaveMoneyHandler();
    saveMoneyHandler.execute();
}

           

輸出結果:

number-00958442164
save 1000
give a praised

           

以上,就是一個簡單的模闆方法的實作。通過使用模闆方法,可以幫助我們很大程度的複用代碼。

因為我們要在銀行辦理很多業務,是以可能需要定義很多的實作類:

//取錢業務的實作類
public class DrawMoneyHandler extends AbstractBusinessHandeler {
    @Override
    public void handle() {
        System.out.println("draw 1000");
    }
}

//理财業務的實作類
public class MoneyManageHandler extends AbstractBusinessHandeler{
    @Override
    public void handle() {
        System.out.println("money manage");
    }
}

           

一直以來,開發者們在使用模闆方法的時候基本都是像上面這個例子一樣:需要準備一個抽象類,将部分邏輯以具體方法以及具體構造函數的形式實作,然後聲明一些抽象方法來讓子類實作剩餘的邏輯。不同的子類可以以不同的方式實作這些抽象方法,進而對剩餘的邏輯有不同的實作。

但是,有了Java 8以後,模闆方法有了另外一種實作方式,不需要定義特别多的實作類了。

Java 8 的函數式程式設計

2014年,Oracle釋出了 Java 8,在Java 8中最大的新特性就是提供了對函數式程式設計的支援。

Java 8在

java.util.function

下面增加增加一系列的函數接口。其中主要有Consumer、Supplier、Predicate、Function等。

本文主要想要介紹一下Supplier和Consumer這兩個,使用者兩個接口,可以幫我們很好的改造模闆方法。這裡隻是簡單介紹下他們的用法,并不會深入展開,如果大家想要學習更多用法,可以自行google一下。

Supplier

Supplier是一個供給型的接口,簡單點說,這就是一個傳回某些值的方法。

最簡單的一個Supplier就是下面這段代碼:

public List<String> getList() {
    return new ArrayList();
}

           

使用Supplier表示就是:

Supplier<List<String>> listSupplier = ArrayList::new;

           

Consumer

Consumer 接口消費型接口,簡單點說,這就是一個使用某些值(如方法參數)并對其進行操作的方法。

最簡單的一個Consumer就是下面這段代碼:

public void sum(String a1) {
    System.out.println(a1);
}

           

使用Consumer表示就是:

Consumer<String> printConsumer = a1 -> System.out.println(a1);

           

Consumer的用法,最見的的例子就是是

Stream.forEach(Consumer)

這樣的用法,

它接受一個Consumer,該Consumer消費正在疊代的流中的元素,并對每個元素執行一些操作,比如列印:

Consumer<String> stringConsumer = (s) -> System.out.println(s.length());
Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);

           

Java 8以後的模闆方法

在介紹過了Java 8中的Consumer、Supplier之後,我們來看下怎麼改造之前我們介紹過的模闆方法。

首先,我們定義一個BankBusinessHandler類,并且重新定義一個execute方法,這個方法有一個入參,是Consumer類型的,然後移除handle方法,重新編排後的模闆方法内容如下:

/**
 * @author Hollis
 */
public class BankBusinessHandler {
    private void execute(Consumer<BigDecimal> consumer) {
        getNumber();

        consumer.accept(null);

        judge();
    }

    private void getNumber() {
        System.out.println("number-00" + RandomUtils.nextInt());
    }

    private void judge() {
        System.out.println("give a praised");
    }
}

           

我們實作的模闆方法execute中,編排了getNumber、judge以及consumer.accept,這裡面consumer.accept就是具體的業務邏輯,可能是存錢、取錢、理财等。需要由其他方法調用execute的時候傳入。

這時候,我們想要實作"存錢"業務的時候,需要BankBusinessHandler類中增加以下方法:

/**
 * @author Hollis
 */
public class BankBusinessHandler {

    public void save(BigDecimal amount) {
        execute(a -> System.out.println("save " + amount));
    }
}

           

在save方法中,調用execute方法,并且在入參處傳入一個實作了"存錢"的業務邏輯的Comsumer。

這樣,我們在執行存錢的業務邏輯的時候,隻需要調用 BankBusinessHandler的save方法即可:

public static void main(String[] args) throws {
    BankBusinessHandler businessHandler = new BankBusinessHandler();
    businessHandler.save(new BigDecimal("1000"));
}

           
number-001736151440
save1000
give a praised

           

如上,當我們想要實作取錢、理财等業務邏輯的時候,和存錢類似:

/**
 * @author Hollis
 */
public class BankBusinessHandler {

    public void save(BigDecimal amount) {
        execute(a -> System.out.println("save " + amount));
    }

    public void draw(BigDecimal amount) {
        execute(a -> System.out.println("draw " + amount));
    }

    public void moneyManage(BigDecimal amount) {
        execute(a -> System.out.println("draw " + amount));
    }
}

           

可以看到,通過使用Java 8中的Comsumer,我們把模闆方法改造了,改造之後不再需要抽象類、抽象方法,也不再需要為每一個業務都建立一個實作類了。我們可以把所有的業務邏輯内聚在同一個業務類中。這樣非常友善這段代碼的後期運維。

前面介紹如何使用Consumer進行改造模闆方法,那麼Supplier有什麼用呢?

我們的例子中,在取号、辦業務、評價這三個步驟中,辦業務是需要根據業務情況進行定制的,是以,我們在模闆方法中,把辦業務這個作為擴充點開放給外部。

有這樣一種情況,那就是現在我們辦業務的時候,取号的方式也不一樣,可能是到銀行網點取号、在網上取号或者銀行客戶經理預約的無需取号等。

無論取号的方式如何,最終結果都是取一個号;而取到的号的種類不同,可能接收到的具體服務也不同,比如vip号會到VIP櫃台辦理業務等。

想要實作這樣的業務邏輯,就需要使用到Supplier,Supplier是一個"供給者",他可以用來定制"取号邏輯"。

首先,我們需要改造下模闆方法:

/**
 * 模闆方法
 */
protected void execute(Supplier<String> supplier, Consumer<BigDecimal> consumer) {

    String number = supplier.get();
    System.out.println(number);


    if (number.startsWith("vip")) {
        //Vip号配置設定到VIP櫃台
        System.out.println("Assign To Vip Counter");
    }
    else if (number.startsWith("reservation")) {
        //預約号配置設定到專屬客戶經理
        System.out.println("Assign To Exclusive Customer Manager");
    }else{
        //預設配置設定到普通櫃台
        System.out.println("Assign To Usual Manager");
    }

    consumer.accept(null);

    judge();
}

           

經過改造,execute的入參增加了一個supplier,這個supplier可以提供一個号碼。至于如何取号的,交給調用execute的方法來執行。

之後,我們可以定義多個存錢方法,分别是Vip存錢、預約存錢和普通存錢:

public class BankBusinessHandler extends AbstractBusinessHandler {

    public void saveVip(BigDecimal amount) {
        execute(() -> "vipNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
    }

    public void save(BigDecimal amount) {
        execute(() -> "number-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
    }

    public void saveReservation(BigDecimal amount) {
        execute(() -> "reservationNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
    }

}

           

在多個不同的存錢方法中,實作不同的取号邏輯,把取号邏輯封裝在supplier中,然後傳入execute方法即可。

測試代碼如下:

BankBusinessHandler businessHandler = new BankBusinessHandler();
businessHandler.saveVip(new BigDecimal("1000"));

           
vipNumber-001638110566
Assign To Vip Counter
save 1000
give a praised

           

以上,我們就是用Comsumer和Supplier改造了模闆方法模式。

使用Java 8對模闆方法進行改造之後,可以進一步的減少代碼量,至少可少建立很多實作類,大大的減少重複代碼,提升可維護性。

當然,這種做法也不是十全十美的,有一個小小的缺點,那就是了解成本稍微高一點,對于那些對函數式程式設計不太熟悉的開發者來說, 上手成本稍微高了一些。。。

總結

以上,我們介紹了什麼是模闆方法模式,以及如何使用Comsumer和Supplier改造模闆方法模式。

這樣的做法是我們日常開發中經常會用到的,其實,我覺得本文中的例子并不是完完全全能表達出來我想表達的意思,但是我們的真實業務中的邏輯講起來又比較複雜。

是以,這就需要大家能夠多多了解并且實踐一下。如果你代碼中用到過模闆方法模式,那一定是可以通過本文中的方法進行改造的。

如果你還沒用過模闆方法模式,那說明你的應用中一定有很多重複代碼,那就趕緊用起來。

作為一個開發工程師,我們要盡最大努力的消滅應用中的重複代碼,功在當代,利在千秋!