分享

模板方法模式实践

 逍遥302 2017-08-28

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

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


2013 年世锦赛

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

   

本文转自:简书

微信号:IdeaofSE


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多