在實際編程中,會經(jīng)常遇到多個類中的某些方法實現(xiàn)邏輯類似的情況,這時我們可以將這些類中的相同部分抽象到父類中,對于有差異的地方,子類根據(jù)自身的實際需求來各自實現(xiàn)。

以羽毛球運動為例,打球必有發(fā)接發(fā)環(huán)節(jié),發(fā)球分正手和反手兩種(這里不談論羽球技術細節(jié)),一般男單反手發(fā)球,女單正手發(fā)球,但發(fā)接發(fā)這個環(huán)節(jié)的流程是一致的。


 

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

abstract class Badminton
{    public abstract void Serve();    public abstract void Catch();    public abstract void Play();
}class MenSingle : Badminton
{    public override void Serve()
    {
        Console.WriteLine("反手發(fā)球......");
    }    public override void Catch()
    {
        Console.WriteLine("正手推底線");
    }    public override void Play()
    {
        Serve();
        Catch();
    }
}class WomenSingle : Badminton
{    public override void Serve()
    {
        Console.WriteLine("正手發(fā)球.......");
    }    public override void Catch()
    {
        Console.WriteLine("軟壓一拍");
    }    public override void Play()
    {
        Serve();
        Catch();
    }
}

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

 

程序開發(fā)中有個重要的原則:Don't repeat yourself。而上面一段代碼中,子類MenSingleWomenSingle中的Play方法是重復的,羽毛球運動除男單、女單外還有男雙,女雙,混雙,如此則代碼中至少五處重復,這顯然不利于日后維護。
接下來對代碼進行改進:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

abstract class Badminton
{    protected abstract void Serve();    protected abstract void Catch();    public void Play()
    {
        Serve();
        Catch();
    }
}class MenSingle : Badminton
{    protected override void Serve()
    {
        Console.WriteLine("反手發(fā)球......");
    }    protected override void Catch()
    {
        Console.WriteLine("正手推底線");
    }

}class WomenSingle : Badminton
{    protected override void Serve()
    {
        Console.WriteLine("正手發(fā)球.......");
    }    protected override void Catch()
    {
        Console.WriteLine("軟壓一拍");
    }

}

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

 

這段代碼將Play方法放到父類中實現(xiàn),對于有差異的ServeCatch則交有子類實現(xiàn),這邊是模板方法模式,封裝不變部分,擴展可變部分。其中Play方法稱之為模板方法,ServeCatch稱為基本方法。
通常模板方法(可以有多個)在父類中實現(xiàn)并調用基本方法以完成固定的邏輯,且不允許子類重寫。
基本方法一般為抽象方法,由子類來完成具體的實現(xiàn)?;痉椒ǖ脑L問修飾符通常是protected,不需要對外界暴露(迪米特法則),客戶端只需要調用模板方法即可。

那么,問題來了,世界羽聯(lián)沒有規(guī)定男單必須用反手發(fā)球,女單必須正手發(fā)球。如果男單想用正手發(fā)球怎么辦?為適應這種有著多種可能的場景,我們對代碼稍作調整:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

abstract class Badminton
{    private  void ForehandServe()
    {
        Console.WriteLine("正手發(fā)球.......");
    }    private void BackhandServe()
    {
        Console.WriteLine("反手發(fā)球......");
    }    protected abstract void Catch();    protected abstract bool IsForeHandServe { get; }    public void Play()
    {        if (IsForeHandServe)
        {
            ForehandServe();
        }        else
        {
            BackhandServe();
        }
        Catch();
    }
}class MenSingle : Badminton
{    protected override bool IsForeHandServe => false;    protected override void Catch()
    {
        Console.WriteLine("正手推底線");
    }
}class WomenSingle : Badminton
{    protected override bool IsForeHandServe => true;    protected override void Catch()
    {
        Console.WriteLine("軟壓一拍");
    }
}

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

 

這里,我們通過在子類中實現(xiàn)屬性IsForehandServe來控制父類中具體調用ForehandServe方法還是調用BackhandServe方法。屬性IsForehandServe稱為鉤子函數(shù),根據(jù)鉤子函數(shù)的不同實現(xiàn),模板方法可以有不同的執(zhí)行結果,即子類對父類產(chǎn)生了影響。

以上,是一個模板方法的杜撰使用場景。模板方法模式有個很重要的特征:父類控制流程,子類負責具體細節(jié)的實現(xiàn)。這里有沒有聯(lián)想到IoC(控制反轉)?IoC的實現(xiàn)方式有多種,DI只是其中之一,模板方法模式也可以。

許多框架(如:ASP.NET MVC)也是這個套路,框架定義一套流程,然后由不同的類負責不同功能的實現(xiàn),并預留擴展點讓開發(fā)人員可根據(jù)實際需求進行擴展開發(fā),但整個框架的處理流程開發(fā)人員是控制不了的。

小結

模板方法模式有以下優(yōu)點:
1、封裝不變部分,擴展可變部分;

寫程序就因該是這樣,不僅僅是在模板方法模式中

2、提取公共部分便于日后維護;

Ctrl + C,Ctrl + V 大法好,但濫用也是要命的

3、父類控制流程,子類負責實現(xiàn);

如此,子類便可通過擴展的方式來增加功能;
同時,對于一些復雜的算法,我們可以現(xiàn)在父類的模板方法中定義好流程,然后再在子類中去實現(xiàn),思路上也會清晰不少;

結語

最后,附一段使用模板方法模式寫的分頁查詢代碼:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

public class DbBase
{    public virtual string TableName
    {        get
        {            throw new NotImplementedException($"屬性:{nameof(TableName)}不得為空!");
        }
    }    protected virtual string ConnectionString
    {        get
        {            throw new NotImplementedException("屬性:" + nameof(ConnectionString) + "不得為空!");
        }
    }    protected SqlConnection CreateSqlConnection()
    {        return CreateSqlConnection(ConnectionString);
    }    protected SqlConnection CreateSqlConnection(string connnectionString)
    {
        SqlConnection dbConnection = new SqlConnection(connnectionString);        if (dbConnection.State == ConnectionState.Closed)
        {
            dbConnection.Open();
        }        return dbConnection;
    }

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

 

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

public interface IPagingQuery<T>    where T : class{    /// <summary>
    /// 數(shù)據(jù)總量    /// </summary>
    int DataCount { get; }    /// <summary>
    /// 分頁查詢    /// </summary>
    /// <param name="pageNumber">頁碼</param>
    /// <param name="pageSize">每頁數(shù)據(jù)量</param>
    /// <returns></returns>
    IEnumerable<T> PagingQuery(int pageNumber, int pageSize);
}

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

public abstract class PagingQueryDalBase<T> : DbBase, IPagingQuery<T>    where T : class{    public int DataCount => GetDataCount();    /// <summary>
    /// 查詢數(shù)據(jù)總數(shù)SQL    /// </summary>
    protected abstract string QueryDataCountSql();    private int GetDataCount()
    {        int dataCount;        using (SqlConnection sqlConnection = base.CreateSqlConnection())
        {            string sql = QueryDataCountSql();
            dataCount = sqlConnection.QueryFirstOrDefault<int>(sql);
        }        return dataCount;
    }    /// <summary>
    /// 分頁查詢SQL    /// </summary>
    protected abstract string PagingQuerySql(int pageNumber, int pageSize);    public IEnumerable<T> PagingQuery(int pageNumber, int pageSize)
    {        if (pageNumber - 1 < 0)
        {            throw new ArgumentException("參數(shù):pageNumber不得小于1");
        }        if (pageSize <= 0)
        {            throw new ArgumentException("參數(shù):pageNumber必須大于0");
        }
        IEnumerable<T> result;        using (SqlConnection sqlConnection = CreateSqlConnection())
        {            string sql = PagingQuerySql(pageNumber, pageSize);
            result = sqlConnection.Query<T>(sql);
        }        return result;
    }
}

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

 

版權聲明

本文為作者原創(chuàng),版權歸作者雪飛鴻所有。 轉載必須保留文章的完整性,且在頁面明顯位置處標明原文鏈接。

如有問題, 請發(fā)送郵件和作者聯(lián)系。

http://www.cnblogs.com/Cwj-XFH/p/7137102.html