模板方法模式实践

简介: 在实际编程中,会经常遇到多个类中的某些方法实现逻辑类似的情况,这时我们可以将这些类中的相同部分抽象到父类中,对于有差异的地方,子类根据自身的实际需求来各自实现。 以羽毛球运动为例,打球必有发接发环节,发球分正手和反手两种(这里不谈论羽球技术细节),一般男单反手发球,女单正手发球,但发接发这个环节的流程是一致的。

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

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


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

 

版权声明

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

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

目录
相关文章
|
设计模式 网络协议 Java
03.建造者模式设计思想
本文详细介绍了建造者模式的设计思想及其应用场景。主要内容包括建造者模式的由来、定义、适用场景及思考,通过实例讲解了如何使用建造者模式解决复杂对象的创建问题。文章还对比了建造者模式与工厂模式的区别,并分析了建造者模式的优缺点。最后,提供了多个相关资源链接,帮助读者深入理解和应用设计模式。
120 1
|
Python
python while true的语法和用法
python while循环语句的一般形式是while后面跟一个条件表达式,当该表达式的返回值为True,或经过布尔转换会返回True,比如1转换为bool布尔类型会为True,那么就执行一次while的循环。while True,即直接把表达式设置为True,那么无论如何,代码都将进行一次while的循环,直到遇到退出的条件,
805 4
|
存储 关系型数据库 MySQL
【MySQL】varbinary 真的比varchar 更合适?
一 前言     在讨论数据表字段设计的时候,有同学提出使用vabinary 代替 varchar ,部分开发不明所以,其实我也是。两者之间具体有什么区别?使用vabinary 代替 varchar 对业务有何优势?本文尝试从性能,数据大小,查询,创建索引等对比功能等方面进行研究,有不妥或者不到位之处还请各位读者朋友提示。
3164 0
|
存储 C# 索引
C#学习相关系列之数据类型类的定义(一)
C#学习相关系列之数据类型类的定义(一)
243 0
|
运维 Serverless
函数计算FC报错问题之SDXL模型报错如何解决
函数计算(Function Compute,FC)是一个事件驱动的全托管计算服务,允许用户编写并上传代码,而无需管理服务器运行和维护;在使用过程中,可能会遇到各种报错,本合集聚焦于函数计算FC常见的报错问题,提供一系列的故障排查指导和解决建议,帮助用户优化云端函数执行
414 0
|
存储
【数据结构】树和二叉树
【数据结构】树和二叉树
|
存储 SQL 分布式计算
首次公开!阿里云开源PolarDB总体架构和企业级特性
在3月2日的阿里云开源 PolarDB 企业级架构发布会上,阿里云 PolarDB 内核技术专家北侠带来了主题为《PolarDB 总体架构设计和企业级特性》的精彩演讲。
31137 1
首次公开!阿里云开源PolarDB总体架构和企业级特性
|
Shell 数据安全/隐私保护
【CTF】靶机:DC-4
本次靶机的知识与前几个靶机略有重叠,不过在信息收集方面有一些难度,值得初学者进行学习。
223 0
|
开发者 Windows Python
多进程的使用| 学习笔记
快速学习多进程的使用
多进程的使用| 学习笔记
|
Kubernetes 负载均衡 网络协议
深入理解K8S网络原理上
深入理解K8S网络原理上
521 0
深入理解K8S网络原理上