• 模板方法模式实践


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

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


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

    版权声明

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

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

  • 相关阅读:
    003random随机数模块
    002---time & datetime
    001---包和模块
    000软件开发规范
    python函数篇
    LeetCode OJ:Construct Binary Tree from Preorder and Inorder Traversal(从前序以及中序遍历结果中构造二叉树)
    LeetCode OJ:Flatten Binary Tree to Linked List(捋平二叉树)
    LeetCode OJ:Convert Sorted Array to Binary Search Tree(将排序好的数组转换成二叉搜索树)
    LeetCode OJ:Count Complete Tree Nodes(完全二叉树的节点数目)
    LeetCode OJ:Validate Binary Search Tree(合法的二叉搜索树)
  • 原文地址:https://www.cnblogs.com/Cwj-XFH/p/7137102.html
Copyright © 2020-2023  润新知