23種設計模式之--模板方法模式

水之原發表於2013-12-05

故事:

輝煌工程—製造悍馬

週三一上班,老大就跑過來叫我開會,原來是一個好訊息,老大終於把XX模型公司的口子開啟了,要我們做悍馬模型,所以老大信心滿滿,說一定要把這個做好,但是開發只有一週的時間,我說這要分析,設計,測試,還要考慮各種效能,一週做不完啊, 老大說只做最基本的實現就可以,既然老大都發話了,那隻能拼命做吧。

既然領導都說了,不考慮擴充套件性,那好辦,先按照最一般的經驗設計類圖:

public abstract class HummerModel
{
    // start car
    public abstract void start();

    // stop car
    public abstract void stop();

    // press Klaxon when you need
    public abstract void alarm();

    // startup engine
    public abstract void engineBoom();

    // run car
    public void run()
    {
        // start car
        start();
        // startup engine
        engineBoom();
        // press Klaxon when you need
        alarm();
        // stop car
        stop();
    }
}
public class HummerH1 extends HummerModel
{
    @Override
    public void alarm()
    {
        System.out.println( "HummerH1 alarming." );
    }

    @Override
    public void engineBoom()
    {
        System.out.println( "HummerH1 startuping engineBoom." );
    }

    @Override
    public void start()
    {
        System.out.println( "HummerH1 starting." );
    }

    @Override
    public void stop()
    {
        System.out.println( "HummerH1 stoping." );
    }

}
public class HummerH2 extends HummerModel
{
    @Override
    public void alarm()
    {
        System.out.println( "HummerH2 alarming." );
    }

    @Override
    public void engineBoom()
    {
        System.out.println( "HummerH2 startuping engineBoom." );
    }

    @Override
    public void start()
    {
        System.out.println( "HummerH2 starting." );
    }

    @Override
    public void stop()
    {
        System.out.println( "HummerH2 stoping." );
    }

}
public class Client
{
    public static void main( String[] args )
    {
        HummerModel hummerH1 = new HummerH1();
        hummerH1.run();
        HummerModel hummerH2 = new HummerH2();
        hummerH2.run();
    }
}

這就是模板方法,看起來是不是很熟悉,呵呵,我們重構時,經常這麼幹。

 

定義:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

定義一個操作中的演算法的框架,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

 

通用類圖:

 

注意:為了防止惡意的操作,一般模板方法都加上final關鍵字,不允許被覆寫。

 

public abstract class AbstractClass
{
    // base method
    protected abstract void doSomething();

    // base method
    protected abstract void doAnything();

    // template method
    public void templateMethod()
    {
        //invoking base method 
        doSomething();
        doAnything();
    }
}
public class ConcreateClass1 extends AbstractClass
{
    //implements base methods
    @Override
    protected void doAnything()
    {
        // handel business
    }

    @Override
    protected void doSomething()
    {
        // handel business
    }
}
public class ConcreateClass2 extends AbstractClass
{
    // implements base methods
    @Override
    protected void doAnything()
    {
        // handel business
    }

    @Override
    protected void doSomething()
    {
        // handel business
    }
}
public class Client
{
    public static void main( String[] args )
    {
        AbstractClass concreateClass1 = new ConcreateClass1();
        AbstractClass concreateClass2 = new ConcreateClass2();
        // invoking template method
        concreateClass1.templateMethod();
        concreateClass2.templateMethod();
    }
}

注意: 抽象模板中的基本方法儘量設計為protected型別,符合迪米特法則,不需要暴露的屬性或方法不要設定為protected型別。實現類若非必要,儘量不要擴大父類中的訪問許可權。

 

優點:

1.封裝不變部分, 擴充套件可變部分。

把認為是不變的部分的演算法封裝到父類實現, 而可變部分的則可以通過繼承來繼續擴充套件。

2.提取公共程式碼, 便於維護。

3.行為由父類控制 ,子類實現。

 

缺點:

按照我們的設計習慣, 抽象類負責宣告最抽象、最一般的事物屬性和方法,實現類完成具體的事物屬性和方法。但是模板方法模式去顛倒了,這對新手來說,會帶來程式碼閱讀的難度。

 

使用場景:

1.多個子類有公有的方法,並且邏輯基本相同時。

2.重要、複雜的演算法,可以把核心演算法設計為模板方法,周邊的相關細節功能則由各個子類實現。

3.重構時,模板方法模式是一個經常使用的模式,把相同的程式碼抽取到父類中,然後通過鉤子函式(見“模板方法模式的擴充套件”)約束其行為。

 

模板方法模式的擴充套件

到目前為止, 兩個模型都執行穩定,突然有一天, 老大急匆匆地找到了我:

“看你怎麼設計的,車子一啟動,喇叭就狂響,吵死人了,客戶提出H1型號的悍馬喇叭想讓它響就響,H2型號的喇叭不要有聲音,趕快修改一下。”

 

自己惹的禍,就要想辦法解決它,稍稍思考一下,解決辦法有了, 先畫出類圖

 

public abstract class HummerModel
{
    private boolean alarmFlag = true;

    // start car
    public abstract void start();

    // stop car
    public abstract void stop();

    // press Klaxon when you need
    public abstract void alarm();

    // startup engine
    public abstract void engineBoom();

    // is need alarm
    protected boolean isAlarm()
    {
        // defult need
        return alarmFlag;
    }

    // customer decision is need alarm
    public void setAlarm( boolean isAlarm )
    {
        alarmFlag = isAlarm;
    }

    // run car
    public void run()
    {
        // start car
        start();
        // startup engine
        engineBoom();
        // press Klaxon when you need
        if ( isAlarm() )
        {
            alarm();
        }
        // stop car
        stop();
    }
}
public class HummerH2 extends HummerModel
{
    @Override
    public void alarm()
    {
        System.out.println( "HummerH2 alarming." );
    }

    @Override
    public void engineBoom()
    {
        System.out.println( "HummerH2 startuping engineBoom." );
    }

    @Override
    public void start()
    {
        System.out.println( "HummerH2 starting." );
    }

    @Override
    public void stop()
    {
        System.out.println( "HummerH2 stoping." );
    }

    @Override
    protected boolean isAlarm()
    {
        return false;
    }
}

setAlarm是一個鉤子方法,使用者可以通過這個方法,控制喇叭是否要響。

 

最佳實踐

父類呼叫子類的方法的三種方式:

1.   把子類傳遞到父類的有參構造中,然後呼叫。

2.   使用反射的方式呼叫, 你使用了反射還有誰不能呼叫的?!

3.   父類呼叫子類的靜態方法

父類呼叫子類的方法其實是不允許的,這個問題我們可以換個角度去理解, 父類建立框架,子類在重寫了父類部分的方法後,再呼叫從父類繼承的方法,產生不同的結果(這正是模板方法模式)。

相關文章