博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
模板方法模式实践
阅读量:7234 次
发布时间:2019-06-29

本文共 5561 字,大约阅读时间需要 18 分钟。

在实际编程中,会经常遇到多个类中的某些方法实现逻辑类似的情况,这时我们可以将这些类中的相同部分抽象到父类中,对于有差异的地方,子类根据自身的实际需求来各自实现。

以羽毛球运动为例,打球必有发接发环节,发球分正手和反手两种(这里不谈论羽球技术细节),一般男单反手发球,女单正手发球,但发接发这个环节的流程是一致的。

 
abstract class Badminton{    public abstract void Serve();    public abstract void Catch();    public abstract void Play();}class MenSingle : Badminton{    public override void Serve()    {        Console.WriteLine("反手发球......");    }    public override void Catch()    {        Console.WriteLine("正手推底线");    }    public override void Play()    {        Serve();        Catch();    }}class WomenSingle : Badminton{    public override void Serve()    {        Console.WriteLine("正手发球.......");    }    public override void Catch()    {        Console.WriteLine("软压一拍");    }    public override void Play()    {        Serve();        Catch();    }}

 

程序开发中有个重要的原则:Don't repeat yourself。而上面一段代码中,子类MenSingleWomenSingle中的Play方法是重复的,羽毛球运动除男单、女单外还有男双,女双,混双,如此则代码中至少五处重复,这显然不利于日后维护。

接下来对代码进行改进:

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("反手发球......");    }    protected override void Catch()    {        Console.WriteLine("正手推底线");    }}class WomenSingle : Badminton{    protected override void Serve()    {        Console.WriteLine("正手发球.......");    }    protected override void Catch()    {        Console.WriteLine("软压一拍");    }}

 

这段代码将Play方法放到父类中实现,对于有差异的ServeCatch则交有子类实现,这边是模板方法模式,封装不变部分,扩展可变部分。其中Play方法称之为模板方法,ServeCatch称为基本方法。

通常模板方法(可以有多个)在父类中实现并调用基本方法以完成固定的逻辑,且不允许子类重写。
基本方法一般为抽象方法,由子类来完成具体的实现。基本方法的访问修饰符通常是protected,不需要对外界暴露(迪米特法则),客户端只需要调用模板方法即可。

那么,问题来了,世界羽联没有规定男单必须用反手发球,女单必须正手发球。如果男单想用正手发球怎么办?为适应这种有着多种可能的场景,我们对代码稍作调整:

abstract class Badminton{    private  void ForehandServe()    {        Console.WriteLine("正手发球.......");    }    private void BackhandServe()    {        Console.WriteLine("反手发球......");    }    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("软压一拍");    }}

 

这里,我们通过在子类中实现属性IsForehandServe来控制父类中具体调用ForehandServe方法还是调用BackhandServe方法。属性IsForehandServe称为钩子函数,根据钩子函数的不同实现,模板方法可以有不同的执行结果,即子类对父类产生了影响。

以上,是一个模板方法的杜撰使用场景。模板方法模式有个很重要的特征:父类控制流程,子类负责具体细节的实现。这里有没有联想到IoC(控制反转)?IoC的实现方式有多种,DI只是其中之一,模板方法模式也可以。

许多框架(如:ASP.NET MVC)也是这个套路,框架定义一套流程,然后由不同的类负责不同功能的实现,并预留扩展点让开发人员可根据实际需求进行扩展开发,但整个框架的处理流程开发人员是控制不了的。

小结

模板方法模式有以下优点:

1、封装不变部分,扩展可变部分;

写程序就因该是这样,不仅仅是在模板方法模式中

2、提取公共部分便于日后维护;

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

3、父类控制流程,子类负责实现;

如此,子类便可通过扩展的方式来增加功能;

同时,对于一些复杂的算法,我们可以现在父类的模板方法中定义好流程,然后再在子类中去实现,思路上也会清晰不少;

结语

最后,附一段使用模板方法模式写的分页查询代码:

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;    }

 

public interface IPagingQuery
where T : class{ ///
/// 数据总量 /// int DataCount { get; } ///
/// 分页查询 /// ///
页码 ///
每页数据量 ///
IEnumerable
PagingQuery(int pageNumber, int pageSize);}
public abstract class PagingQueryDalBase
: DbBase, IPagingQuery
where T : class{ public int DataCount => GetDataCount(); ///
/// 查询数据总数SQL /// protected abstract string QueryDataCountSql(); private int GetDataCount() { int dataCount; using (SqlConnection sqlConnection = base.CreateSqlConnection()) { string sql = QueryDataCountSql(); dataCount = sqlConnection.QueryFirstOrDefault
(sql); } return dataCount; } ///
/// 分页查询SQL /// protected abstract string PagingQuerySql(int pageNumber, int pageSize); public IEnumerable
PagingQuery(int pageNumber, int pageSize) { if (pageNumber - 1 < 0) { throw new ArgumentException("参数:pageNumber不得小于1"); } if (pageSize <= 0) { throw new ArgumentException("参数:pageNumber必须大于0"); } IEnumerable
result; using (SqlConnection sqlConnection = CreateSqlConnection()) { string sql = PagingQuerySql(pageNumber, pageSize); result = sqlConnection.Query
(sql); } return result; }}

 

版权声明

本文为作者原创,版权归作者所有。 转载必须保留文章的完整性,且在页面明显位置处标明。

如有问题, 请和作者联系。

你可能感兴趣的文章
docker数据拷贝
查看>>
shiro realm 注解失败问题解决过程
查看>>
iOS 静态库,动态库与 Framework 浅析
查看>>
Java对ArrayList进行排序
查看>>
NumberFormat
查看>>
Spring WebSocket初探1 (Spring WebSocket入门教程)<转>
查看>>
winform按钮和子按钮
查看>>
C#回顾 –6.特性
查看>>
Spring和cxf3的整合,以maven的方式
查看>>
Apache Shiro系列三,概述 —— 10分钟入门
查看>>
servlet生命周期
查看>>
Java 网络编程
查看>>
数据库的物理结构和逻辑结构
查看>>
Hadoop MapReduce编程 API入门系列之挖掘气象数据版本3(九)
查看>>
Hadoop HDFS编程 API入门系列之合并小文件到HDFS(三)
查看>>
【MyEcplise】build workspace卡死
查看>>
基于资源的权限系统-API设计
查看>>
如何区分USB 2.0 和USB 3.0插口
查看>>
排序及重复元素去重的说明,TreeSet,HashSet
查看>>
SQLServer 维护脚本分享(05)内存(Memory)
查看>>