模板方法模式实践

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

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

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<T>
    where T : class
{
    /// <summary>
    /// 数据总量
    /// </summary>
    int DataCount { get; }

    /// <summary>
    /// 分页查询
    /// </summary>
    /// <param name="pageNumber">页码</param>
    /// <param name="pageSize">每页数据量</param>
    /// <returns></returns>
    IEnumerable<T> PagingQuery(int pageNumber, int pageSize);
}
public abstract class PagingQueryDalBase<T> : DbBase, IPagingQuery<T>
    where T : class
{
    public int DataCount => GetDataCount();

    /// <summary>
    /// 查询数据总数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("参数:pageNumber不得小于1");
        }

        if (pageSize <= 0)
        {
            throw new ArgumentException("参数:pageNumber必须大于0");
        }
        IEnumerable<T> result;
        using (SqlConnection sqlConnection = CreateSqlConnection())
        {
            string sql = PagingQuerySql(pageNumber, pageSize);
            result = sqlConnection.Query<T>(sql);
        }
        return result;
    }
}

版权声明

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

如有问题, 请发送邮件和作者联系。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏DOTNET

asp.net web api 版本控制

版本控制 版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通...

5816
来自专栏马洪彪

C#解析PDF

C#解析PDF的方式有很多,比较好用的有ITestSharp和PdfBox。 PDF内容页如果是图片类型,例如扫描件,则需要进行OCR(光学字符识别)。 文本内...

5339
来自专栏跟着阿笨一起玩NET

如何让DevExpress TreeList的每个结点高亮显示?

原文地址:http://www.devexpresscn.com/devResources/page-18-78.html

1702
来自专栏海天一树

小朋友学C语言(29):switch case语句

switch case语句与if elseif语句类似,都是从多个选择条件里选取一个来执行。 (一)先来看一个if elseif程序 #include <std...

3139
来自专栏XAI

百度人脸识别API Java调用

工具类下载http://pan.baidu.com/s/1jIuo0N8 小Demo查询。 1.官网文档必须看 http://ai.baidu.com/docs...

2.2K11
来自专栏草根专栏

从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD

Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-...

4056
来自专栏大内老A

我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承

当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解。其中很大一部分人甚至认为面向服务将是面向对象的终结...

1905
来自专栏偏前端工程师的驿站

网页优化系列三:使用压缩后置viewstate

  Asp.net中的服务器控件都启用了viewstate,虽然方便了开发人员,但页面大小及性能上确实有所影响,对于无需viewstate的控件及页面可以直接把...

2125
来自专栏偏前端工程师的驿站

网页优化系列三:使用压缩后置viewstate

  Asp.net中的服务器控件都启用了viewstate,虽然方便了开发人员,但页面大小及性能上确实有所影响,对于无需viewstate的控件及页面可以直接把...

2136
来自专栏函数式编程语言及工具

FunDA(16)- 示范:整合并行运算 - total parallelism solution

   在对上两篇讨论中我们介绍了并行运算的两种体现方式:并行构建数据源及并行运算用户自定义函数。我们分别对这两部分进行了示范。本篇我准备示范把这两种情况集成一体...

20310

扫码关注云+社区

领取腾讯云代金券