当前位置: 首页 > news >正文

学习设计模式《十五》——模板方法模式

一、基础概念

        模板方法模式的本质是【固定算法骨架】

        模板方法模式的定义定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

认识模板方法模式
序号模板方法模式内容说明
1模板方法模式的功能在于固定算法骨架,而让具体算法可以实现扩展(这在实际应用中非常广泛,尤其是在设计框架级功能的时候非常有用;框架定义好了算法的步骤,在合适的点让开发人员进行扩展,实现具体的算法(比如:DAO实现设计通用的增删改查功能))。
《1》模板方法模式还额外提供了一个好处,就是可以控制子类的扩展(因为在父类中定义好了算法步骤,只是在某几个固定的点才会调用到被子类。
《2》实现的方法,因此也就只允许在这几个点来扩展功能【这些可以被子类覆盖以扩展功能的方法通常被称为钩子方法】
2为何不是接口【为什么模板方法的模板不使用接口而使用抽象方法】?《1》接口是一个特殊的抽象类,所有接口中的属性自动是常量【即:public final static】而所有接口中的方法必须是抽象的。
《2》抽象类简单的说是用abstract修饰的类【抽象类不一定包含抽象方法;但有抽象方法的类一定是抽象类】;
《3》接口和抽象类相比较,最大的特点是【接口中所有的方法都没有具体的实现】【而抽象类中是可以有具体的实现方法的】
通常在【既要约束子类的行为,又要为子类提供公共功能的时候使用抽象类】(此处的模板方法模式需要固定定义算法的骨架
  该骨架应该只有一份,算是公共行为;但是其中具体的步骤实现又可能是各不相同的,这恰好符合抽象类的原则)
3变与不变程序设计的一个很重要思考点就是“变与不变”也就是分析程序中哪些功能是可变的,哪些是不可变的,然后把不可变的部分抽象出来,进行公共实现把变化的部分分离出去,用接口来封装隔离,或是用抽象类来约束子类行为
4好莱坞法则:简单的说就是【不要找我们,我们会联系你】模板方法模式很好的体现了这一点,作为父类的模板会在需要的时候,调用子类相应的方法,也就是由父类来找子类实现,而不是让子类找父类来实现;这也是一种反向控制结构(按照通常的思路,是子类找父类才对,也就是应该子类调用父类方法,父类根本不知道子类,而子类知道父类。但是在模板方法模式里面,则是父类找子类,所以是一种反向控制结构)
5扩展登录控制在使用模板方法模式实现后,如果想要扩展新功能,有如下两种情况:
《1》只需提供新的子类实现就可以了(如:想要切换不同的加密算法,只需要新创建一个类并覆写父类的加密方法即可,已有的实现不需要任何改动)
《2》既要加入新的功能,也需要新的数据(如:现在对于普通用户登录要实现一个加强版,要求登录人员除了编号和密码外,还需要提供注册时留下的验证问题和验证答案,验证问题和验证答案记录在数据库中,而不是验证码,一般web开发中登录使用的验证码会存放到session中)
6模板的写法实现模板的时候,到底哪些方法实现在模板上?模板能不能全部实现了?
《1》模板的方法:定义算法骨架的方法
《2》具体的操作:在模板中直接实现某些步骤的方法。通常这些步骤的实现算法是固定的,而且是不怎么变化的,因此可以将其当做公共功能实现在模板中。
        如果不需要为子类提供访问这些方法的话,还是可以private的。这样一来,子类的实现就相对简单些。
        如果是子类需要访问的,可以把这些方法定义为protected的,因为通常情况下,这些实现不能被子类覆盖和改变了。
《3》具体的AbstractClass操作:在模板中实现某些公共功能,可以提提供给子类使用,一般不是具体的算法步骤实现,而是一些辅助的公共功能
《4》原语操作:就是在模板中定义的抽象操作,通常是模板方法需要调用的操作,且是必须的操作,而且在父类中还没有办法确定下来如何实现,需要子类来真正实现的方法
《5》钩子操作:在模板中定义,并提供默认实现的操作,这些方法通常被视为可扩展的点,但不是必须的,子类可以有选择地覆写这些方法,以提供新的实现来扩展功能。
《6》Factory Method:在模板方法中,如果需要得到某些对象实例,可以考虑通过工厂方法来获取,把具体的构建对象实现延迟到子类中去。    

        何时选用模板方法模式?

        1、需要固定定义算法骨架(实现一个算法的不变的部分,并把可变的行为留给子类来实现)
        2、各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复
        3、需要控制子类扩展的情况,模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展

二、模板方法模式示例

        业务需求:现在有一个基于Web的企业级应用系统,需要实现两种登录控制,直接使用不同的登录页面来区分它们:

1、普通用户登录前台控制功能:

        《1》前台页面:用户输入用户名和密码,提交登录请求,让系统进行登录控制;

        《2》后端服务:从数据库获取登录人员信息;

        《3》后端服务:判断从前台传递过来的登录数据和数据库中的已有数据进行匹配;

        《4》前台动作:如果匹配则转向首页,不匹配则返回登录页面,并提示错误信息。

2、工作人员登录后台的登录控制功能:

        《1》前台页面:用户输入用户名和密码,提交登录请求,让系统进行登录控制;

        《2》后端服务:从数据库获取登录人员信息;

        《3》后端服务:判断从前台传递过来的密码数据使用相应的算法进行加密,得到加密后的密码数据;

        《4》后端服务:判断从前台传递过来的用户名和加密后的密码数据与数据库中已有的数据是否匹配;

        《5》前台动作:如果匹配则转向首页,不匹配则返回登录页面,并提示错误信息。 

        注意:普通用户和工作人员数据在数据库中是存储在不同的表里面的,需要不同的模块来维护普通用户和工作人员的数据,且工作人员的密码是加密存放的。

 2.1、不使用模式的示例

        由于普通用户和工作人员登录时不同的模块,有不同的页面、不同的处理逻辑和不同的数据存储,所以在实现上完全作为两个独立的小模块来实现。

  2.1.1、网站登录的对象模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern
{/// <summary>/// 描述登录人员登录时填写的信息数据模型/// </summary>internal class LoginModel{public string? UserId { get; set; }public string? Password { get; set; }}//Class_end
}

  2.1.2、普通用户的模型对象和登录处理逻辑 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern
{/// <summary>/// 描述登录人员登录时填写的模型/// </summary>internal class UserModel{public string? Uuid { get; set; }public string? UserId { get; set; }public string? UserName { get; set;}public string? Password { get; set; }}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern
{/// <summary>/// 普通用户登录控制的逻辑/// </summary>internal class NormalLogin{/// <summary>/// 判断登录数据是否正确,正确则可以登录/// </summary>/// <param name="loginModel"></param>/// <returns></returns>public bool Login(LoginModel loginModel){//模拟从数据库获取登录人员的信息【根据用户编号获取人员信息】UserModel userModel = GetUserInfoByUserId(loginModel.UserId);if (userModel != null){if (userModel.UserId.Equals(loginModel.UserId) && userModel.Password.Equals(loginModel.Password)){return true;}}return false;}//根据用户编号获取用户详情private UserModel GetUserInfoByUserId(string userId){UserModel userModel = new UserModel();userModel.Uuid = Guid.NewGuid().ToString("D");userModel.UserId = userId;userModel.UserName = "Test";userModel.Password = "123456";return userModel;}}//Class_end
}

  2.1.3、工作人员模型对象和登录控制逻辑

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern
{/// <summary>/// 工作人员的模型/// </summary>internal class WorkerModel{public string? Uuid { get; set; }public string? WorkerId { get; set; }public string? UserName { get; set; }public string? Password { get; set; }}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern
{/// <summary>/// 工作人员登录的控制逻辑/// </summary>internal class WorkerLogin{/// <summary>/// 判断登录数据是否正确,正确则可以登录/// </summary>/// <param name="loginModel"></param>/// <returns></returns>public bool Login(LoginModel loginModel){WorkerModel workerModel = GetWorkerInfoByWorkerId(loginModel.UserId);if (workerModel!=null){string encryptPwd = EncryptPassword(loginModel.Password);if (workerModel.WorkerId.Equals(loginModel.UserId)&&workerModel.Password.Equals(encryptPwd)){return true;}}return false;}//对密码加密private string EncryptPassword(string password){if (password == null) return null;//这里执行对应的加密逻辑,此处以简单的MD5示意,不建议生产环境使用MD5 md5 = new MD5CryptoServiceProvider();byte[] bytes = Encoding.Default.GetBytes(password);byte[] encryptdata = md5.ComputeHash(bytes);string str = Convert.ToBase64String(encryptdata);return str;}//根据工作人员编号获取工作人员详情private WorkerModel GetWorkerInfoByWorkerId(string userId){WorkerModel workerModel = new WorkerModel();workerModel.Uuid = Guid.NewGuid().ToString("D");workerModel.WorkerId = userId;workerModel.UserName = "worker01";//注意正常我们从数据库获取的密码数据是加密的(这里只做演示)workerModel.Password = "JfnnlDI7RTiF9RgfG2JNCw==";return workerModel;}}//Class_end
}

  2.1.4、客户端测试

namespace TemplateMethodPattern
{internal class Program{static void Main(string[] args){LoginTest();Console.ReadLine();}/// <summary>/// 测试/// </summary>private static void LoginTest(){Console.WriteLine("---未使用模板方法模式登录展示---");LoginModel loginModel = new LoginModel();loginModel.UserId = "test";loginModel.Password = "123456789";NormalLogin normalLogin = new NormalLogin();bool res_normalLogin=normalLogin.Login(loginModel);Console.WriteLine($"普通用户登录前台结果【{res_normalLogin}】");WorkerLogin workerLogin = new WorkerLogin();bool res_workerLogin=workerLogin.Login(loginModel);Console.WriteLine($"工作人员登录后台结果【{res_workerLogin}】");Console.WriteLine("\n修改密码后展示");loginModel.Password = "123456";res_normalLogin = normalLogin.Login(loginModel);Console.WriteLine($"普通用户登录前台结果【{res_normalLogin}】");res_workerLogin = workerLogin.Login(loginModel);Console.WriteLine($"工作人员登录后台结果【{res_workerLogin}】");}}//Class_end
}

  2.1.5、运行结果

        我们观察如上的实现可以发现,虽然功能实现了,但两种登录的实现太相似了,现在是完全分开的,当做两个独立的模块实现,如果今后需要扩展功能(如:添加一个编号只能同时登录一次功能)那么两个模块都需要修改,十分麻烦。而且,相似的地方太多,显得很重复,并且具体的实现和判断步骤都混合在一起,不利于今后变换功能(如:需变换加密算法等操作)

总结起来就是两个问题【1、重复或相似代码太多】【2、扩展起来不方便】。

 2.2、使用模板方法模式的示例

我们通过分析需求和不使用模式的实现示例可以发现登录控制的逻辑判断步骤如下:

《1》根据登录人员的编号去获取相应的数据;

《2》获取登录人员填写的密码数据直接返回,或者就是需要对填写的密码数据进行加密后返回;

《3》判断登录人员填写的数据和从数据库中获取的数据是否匹配,匹配则返回登录界面,不匹配则报错。

        且如上这3个登录控制逻辑的步骤中,第一和第三步骤是必须的,第二个步骤是可选的(那么就可以定义一个父类,在其中定义一个方法来定义这个算法骨架,也就是模板方法,然后将父类无法确定实现的内容,延迟到子类实现)。

  2.2.1、统一登录控制所需的数据模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethod
{/// <summary>/// 封装登录所需的数据对象/// </summary>internal class LoginModel{public string LoginId { get; set; }public string Password { get; set; }}//Class_end
}

  2.2.2、定义公共的登录控制算法骨架

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethod
{/// <summary>/// 登录控制逻辑模板/// </summary>internal abstract class LoginTemplate{/// <summary>/// 登录控制逻辑/// </summary>/// <param name="loginModel">用户输入的登录数据对象</param>/// <returns></returns>public bool Login(LoginModel loginModel){LoginModel lm = GetLoginUserInfo(loginModel.LoginId);if (lm != null){//对密码进行加密string encryptPwd=EncryptPassword(loginModel.Password);loginModel.Password = encryptPwd;//比较是否匹配return IsMatch(loginModel,lm);}return false;}/// <summary>/// 根据登录编号获取数据库中对应编号用户的数据/// </summary>/// <param name="loginId">用户编号</param>/// <returns></returns>protected abstract LoginModel GetLoginUserInfo(string loginId);/// <summary>/// 对密码数据进行加密【这里默认不加密】/// </summary>/// <param name="password">用户输入的密码</param>/// <returns>返回加密后的密码</returns>protected virtual string EncryptPassword(string password){return password;}/// <summary>/// 判断用户填写的数据与数据库中的数据是否匹配/// </summary>/// <param name="loginModel">登录对象</param>/// <param name="lm">数据库中的用户数据对象</param>/// <returns></returns>private bool IsMatch(LoginModel loginModel,LoginModel lm){if (loginModel!=null && lm!=null){if (loginModel.LoginId.Equals(lm.LoginId)&&loginModel.Password.Equals(lm.Password)){return true;}}return false;}}//Class_end
}

  2.2.3、实现普通用户的登录控制

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethod
{/// <summary>/// 普通用户登录控制逻辑/// </summary>internal class NormalLogin : LoginTemplate{protected override LoginModel GetLoginUserInfo(string loginId){LoginModel loginModel= new LoginModel();loginModel.LoginId = loginId;loginModel.Password = "123456";return loginModel;}}//Class_end
}

  2.2.4、实现工作人员的登录控制

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethod
{internal class WorkerLogin : LoginTemplate{protected override LoginModel GetLoginUserInfo(string loginId){LoginModel loginModel=new LoginModel();loginModel.LoginId = loginId;loginModel.Password = "JfnnlDI7RTiF9RgfG2JNCw==";return loginModel;}protected override string EncryptPassword(string password){if (password == null) return null;//这里执行对应的加密逻辑,此处以简单的MD5示意,不建议生产环境使用MD5 md5 = new MD5CryptoServiceProvider();byte[] bytes = Encoding.Default.GetBytes(password);byte[] encryptdata = md5.ComputeHash(bytes);string str = Convert.ToBase64String(encryptdata);return str;}}//Class_end
}

  2.2.5、客户端测试


namespace TemplateMethodPattern
{internal class Program{static void Main(string[] args){LoginTemplateTest();Console.ReadLine();}/// <summary>/// 登录模板测试/// </summary>private static void LoginTemplateTest(){Console.WriteLine("\n------使用模板方法模式登录展示------");TemplateMethod.LoginModel loginModel = new TemplateMethod.LoginModel();loginModel.LoginId = "Test";loginModel.Password = "123456789";TemplateMethod.LoginTemplate normalLogin = new TemplateMethod.NormalLogin();TemplateMethod.LoginTemplate workerLogin = new TemplateMethod.WorkerLogin();bool res_normalLogin = normalLogin.Login(loginModel);Console.WriteLine($"普通用户登录前台结果【{res_normalLogin}】");bool res_workerLogin = workerLogin.Login(loginModel);Console.WriteLine($"工作人员登录后台结果【{res_workerLogin}】");Console.WriteLine("\n修改密码后展示");loginModel.Password = "123456";res_normalLogin = normalLogin.Login(loginModel);Console.WriteLine($"普通用户登录前台结果【{res_normalLogin}】");res_workerLogin = workerLogin.Login(loginModel);Console.WriteLine($"工作人员登录后台结果【{res_workerLogin}】");}}//Class_end
}

  2.2.6、运行结果

 2.3、使用模板方法模式进行扩展的示例

        在上面的2.2中已经使用模板方法模式实现了不同人员的登录功能,现在需要实现一个加强版的普通人员登录(即:要求普通登录人员除了输入编号和密码外,还需要提供注册时留下的验证问题和验证答案,验证的问题和答案记录在数据库中):

  2.3.1、新增一个普通用户登录对象继承原有登录对象进行扩展

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodExtand
{/// <summary>/// 封装登录所需的数据对象【原有对象】/// </summary>internal class LoginModel{public string LoginId { get; set; }public string Password { get; set; }}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodExtand
{internal class NormalLoginModel : LoginModel{//密保问题和答案public string? Question { get; set; }public string? Answer { get; set; }}//Class_end
}

  2.3.2、给原有的登录控制面板的匹配方法修改为公有类型且可覆写


using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodExtand
{/// <summary>/// 登录控制逻辑模板/// </summary>internal abstract class LoginTemplate{public bool Login(LoginModel loginModel){LoginModel lm = GetLoginUserInfo(loginModel.LoginId);if (lm != null){//对密码进行加密string encryptPwd = EncryptPassword(lm.Password);//比较是否匹配return IsMatch(loginModel, lm);}return false;}//根据登录编号获取数据库中对应编号用户的数据public abstract LoginModel GetLoginUserInfo(string loginId);/// <summary>/// 对密码数据进行加密【这里默认不加密】/// </summary>/// <param name="password"></param>/// <returns></returns>public virtual string EncryptPassword(string password){return password;}/// <summary>/// 判断用户填写的数据与数据库中的数据是否匹配(修改为公有且可被覆写)/// </summary>/// <param name="loginModel">登录数据</param>/// <param name="lm">数据库存储的数据</param>/// <returns></returns>public virtual bool IsMatch(LoginModel loginModel, LoginModel lm){if (loginModel != null && lm != null){if (loginModel.LoginId.Equals(lm.LoginId) &&loginModel.Password.Equals(lm.Password)){return true;}}return false;}}//Class_end
}

  2.3.3、新增普通用户的登录扩展类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodExtand
{/// <summary>/// 普通用户登录控制逻辑/// </summary>internal class NormalLoginExtand : LoginTemplate{public override LoginModel GetLoginUserInfo(string loginId){NormalLoginModel normalLoginModel = new NormalLoginModel();normalLoginModel.LoginId = loginId;normalLoginModel.Password = "123456";normalLoginModel.Question = "你读过的一所学校名称是?";normalLoginModel.Answer = "xxx大学";return normalLoginModel;}/// <summary>/// 覆写模板的匹配方法(添加上验证问题与答案的校验逻辑)/// </summary>/// <param name="loginModel"></param>/// <param name="lm"></param>/// <returns></returns>public override bool IsMatch(LoginModel loginModel, LoginModel lm){//原有的校验逻辑bool res=base.IsMatch(loginModel, lm);//新增的验证问题和答案校验逻辑if (res){NormalLoginModel nloginModel = (NormalLoginModel)loginModel;NormalLoginModel nlm = (NormalLoginModel)lm;if (nloginModel.Question.Equals(nlm.Question)&& nloginModel.Answer.Equals(nlm.Answer)){return true;}}return false;}}//Class_end
}

  2.3.4、客户端测试


namespace TemplateMethodPattern
{internal class Program{static void Main(string[] args){LoginTemplateExtandTest();Console.ReadLine();}/// <summary>/// 登录模板拓展测试/// </summary>private static void LoginTemplateExtandTest(){Console.WriteLine("\n-----登录模板拓展测试-----");TemplateMethodExtand.NormalLoginModel normalLoginModel = new TemplateMethodExtand.NormalLoginModel();normalLoginModel.LoginId="Test";normalLoginModel.Password= "123456";normalLoginModel.Question = "你读过的一所学校名称是?";normalLoginModel.Answer = "xxx大学";TemplateMethodExtand.LoginTemplate normalLoginExtand= new TemplateMethodExtand.NormalLoginExtand();bool res_normalLoginExtand=normalLoginExtand.Login(normalLoginModel);Console.WriteLine($"普通人员的拓展登录结果【{res_normalLoginExtand}】");//修改问题答案与数据库不一致后normalLoginModel.Answer = "xxx高中";bool res_normalLoginExtand2 = normalLoginExtand.Login(normalLoginModel);Console.WriteLine($"修改后普通人员的拓展登录结果【{res_normalLoginExtand2}】");}}//Class_end
}

  2.3.5、运行结果

  2.4、完整的模板定义示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.CompleteTemplateMethodDemo
{/// <summary>/// 一个较为完整的模板定义示例/// 注意:/// 《1》如果允许所有的类都可以访问这些方法,则可以将它们都定义为public/// 《2》如果只是子类需要访问这些方法,那就使用protected/// </summary>internal abstract class AbstractTemplate{//模板方法,定义算法骨架public void templateMethod(){//第一步操作this.OperationOne();//第二步操作this.OperationTwo();//第三步操作this.DoPrimitiveOperationOne();//第四步操作this.DoPrimitiveOperationTwo();//第五步操作this.HookOperationOne();}/// <summary>/// 具体操作1【算法中必要的实现步骤,是固定的,且子类不需要访问】/// </summary>private void OperationOne(){//具体的实现内容}/// <summary>/// 具体操作2【算法中必要的实现步骤,是固定的,且子类不需要访问】/// </summary>private void OperationTwo(){//具体的实现内容}/// <summary>/// 具体的AbstractClass,子类的公共功能,但通常不是具体的算法步骤/// </summary>protected virtual void CommonOperation(){ //在这里具体实现}/// <summary>/// 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类实现/// </summary>protected abstract void DoPrimitiveOperationOne();/// <summary>/// 原语操作2,算法中的必要步骤,父类无法确定如何真正实现,需要子类实现/// </summary>protected abstract void DoPrimitiveOperationTwo();/// <summary>/// 钩子操作,算法中的步骤,不一定需要,提供默认实现,由子类选择并具体实现/// </summary>protected virtual void HookOperationOne(){//这里提供默认实现}/// <summary>/// 工厂方法,创建某个对象,这里用object代替,在算法实现中可能需要/// </summary>/// <returns></returns>protected abstract Object CreateOneObject();}//Class_end
}

 2.5、通过接口的方式实现父类在运行期间调用子类方法

        标准的模板方法模式中主要是使用继承的方式实现父类在运行期间可以调用子类的方法(让其他类来扩展或具体实现模板中固定的算法骨架中的某些算法步骤)但也可以通过接口中定义方法,然后在调用具体的实现类中的方法

  2.5.1、创建一个接口定义所有可以被扩展的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodInvoke
{/// <summary>/// 登录控制的模板方法需要的回调接口/// </summary>internal interface LoginCallback{/// <summary>/// 根据用户Id获取用户的信息/// </summary>/// <param name="loginId">用户Id</param>/// <returns></returns>LoginModel GetLoginUserInfo(string loginId);/// <summary>/// 对密码数据进行加密/// </summary>/// <param name="password">密码</param>/// <param name="template">LoginTemplate对象,通过它来调用LoginTemplate中定义的公共方法或默认实现</param>/// <returns></returns>string EncryptPassword(string password,LoginTemplate template);/// <summary>/// 判断用户填写的登录数据和存储中对应的数据是否匹配/// </summary>/// <param name="loginModel">用户填写的登录数据</param>/// <param name="lm">数据库中存储的用户数据</param>/// <param name="template">LoginTemplate对象通过调用在LoginTemplate中定义的公共方法或默认实现</param>/// <returns></returns>bool IsMatch(LoginModel loginModel,LoginModel lm,LoginTemplate template);}//Interface_end
}

  2.5.2、修改登录控制模板

这里的登录控制模板变化有:

《1》不再是抽象类,所有的抽象方法都删除;

《2》对模板的Login方法添加一个参数(用于回调接口);

《3》在模板方法实现中,除了在模板中固定的实现外,所有可以被扩展的方法,都应该通过回调接口进行调用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodInvoke
{/// <summary>/// 描述登录人员登录时填写的信息数据模型【不做任何改变】/// </summary>internal class LoginModel{public string LoginId { get; set; }public string Password { get; set; }}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodInvoke
{/// <summary>/// 登录控制逻辑模板/// </summary>internal class LoginTemplate{public bool Login(LoginModel loginModel,LoginCallback loginCallback){LoginModel lm = loginCallback.GetLoginUserInfo(loginModel.LoginId);if (lm != null){//对密码进行加密string encryptPwd =loginCallback.EncryptPassword(loginModel.Password,this);//比较是否匹配loginModel.Password = encryptPwd;return loginCallback.IsMatch(loginModel, lm,this);}return false;}/// <summary>/// 对密码数据进行加密【这里默认不加密】/// </summary>/// <param name="password"></param>/// <returns></returns>public virtual string EncryptPassword(string password){return password;}//判断用户填写的数据与数据库中的数据是否匹配public bool IsMatch(LoginModel loginModel, LoginModel lm){if (loginModel != null && lm != null){if (loginModel.LoginId.Equals(lm.LoginId) &&loginModel.Password.Equals(lm.Password)){return true;}}return false;}}//Class_end
}

  2.5.3、分别实现普通用户登录类和工作人员登录类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodInvoke
{/// <summary>/// 普通人员登录/// </summary>internal class NormalLogin : LoginCallback{public string EncryptPassword(string password, LoginTemplate template){return template.EncryptPassword(password);}public LoginModel GetLoginUserInfo(string loginId){LoginModel model = new LoginModel();model.LoginId = loginId;model.Password = "123456";return model;}public bool IsMatch(LoginModel loginModel, LoginModel lm, LoginTemplate template){//自己不覆盖直接调用模板默认实现return template.IsMatch(loginModel,lm);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodInvoke
{/// <summary>/// 工作人员登录/// </summary>internal class WorkerLogin : LoginCallback{public string EncryptPassword(string password, LoginTemplate template){//这里实现接口的真正加密方法,不使用模板的加密方法if (string.IsNullOrEmpty(password)) return null;//这里执行对应的加密逻辑,此处以简单的MD5示意,不建议生产环境使用MD5 md5 = new MD5CryptoServiceProvider();byte[] bytes = Encoding.Default.GetBytes(password);byte[] encryptdata = md5.ComputeHash(bytes);string str = Convert.ToBase64String(encryptdata);return str;}public LoginModel GetLoginUserInfo(string loginId){LoginModel loginModel = new LoginModel();loginModel.LoginId = loginId;loginModel.Password = "123456789";return loginModel;}public bool IsMatch(LoginModel loginModel, LoginModel lm, LoginTemplate template){//直接调用模板的的匹配方法return template.IsMatch(loginModel,lm);}}//Class_end
}

  2.5.4、客户端测试


namespace TemplateMethodPattern
{internal class Program{static void Main(string[] args){LoginTemplateInvokeTest();Console.ReadLine();}/// <summary>/// 登录模板回调测试/// </summary>private static void LoginTemplateInvokeTest(){Console.WriteLine("\n------登录模板回调测试------");TemplateMethodInvoke.LoginModel loginModel=new TemplateMethodInvoke.LoginModel();loginModel.LoginId="Test";loginModel.Password = "123456";TemplateMethodInvoke.LoginTemplate loginTemplate=new TemplateMethodInvoke.LoginTemplate();bool res_normalLogin = loginTemplate.Login(loginModel, new TemplateMethodInvoke.NormalLogin());Console.WriteLine($"进行普通用户登录结果【{res_normalLogin}】");bool res_workerLogin = loginTemplate.Login(loginModel, new TemplateMethodInvoke.WorkerLogin());Console.WriteLine($"工作人员登录结果【{res_workerLogin}】");Console.WriteLine("\n修改密码后展示");loginModel.Password = "123456789";res_normalLogin = loginTemplate.Login(loginModel, new TemplateMethodInvoke.NormalLogin());Console.WriteLine($"进行普通用户登录结果【{res_normalLogin}】");res_workerLogin = loginTemplate.Login(loginModel, new TemplateMethodInvoke.WorkerLogin());Console.WriteLine($"工作人员登录结果【{res_workerLogin}】");}}//Class_end
}

  2.5.5、运行结果

 2.6、典型的模板方法模式应用——List排序

在C#的System.Collections.Generic命名空间下的List类,实现了对列表的排序,提供了一个Sort方法,接受一个列表和一个Comparison的实例,这个方法实现的大致步骤如下:

《1》先把列表转为对象数组;

《2》通过sort方法雷队数组进行排序,传入Comparison实例;

《3》然后再把排序好的数组数据设置回到原来的列表对象中去。

其中算法的步骤是固定的,只是其中具体的比较数据大小的步骤需要由外部来提供(即:需要外部传入Comparison的实例);如果Comparison实例的Compare()方法返回一个小于0的数,表示被比较的两个对象中,前面的对象小于后面的对象,如果返回一个等于0的数,则表示比较的两个对象相等;如果返回一个大于0的数,则表示比较的两个数中,前面的对象大于后面的对象。

  2.6.1、定义对象模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodSort
{/// <summary>/// 用户模型/// </summary>internal class UserModel{public string? Id { get; set; } = Guid.NewGuid().ToString("N");public string? Name { get; set; }public int Age { get; set; }public UserModel(string name,int age){this.Name= name;this.Age= age;}public override string ToString(){string str = $"编号【{this.Id}】姓名【{this.Name}】年龄【{this.Age}】";return str ;}}//Class_end
}

  2.6.2、列表排序和比较方法的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TemplateMethodPattern.TemplateMethodSort
{internal class ListSort{//排序public void SortTest(){List<UserModel> userModelList = GetUserModelList();Console.WriteLine("------排序前------");Print(userModelList);Console.WriteLine("------排序后------");userModelList.Sort(Compare);Print(userModelList);}//获取用户列表private List<UserModel> GetUserModelList(){UserModel userModel1 = new UserModel("张三", 23);UserModel userModel2 = new UserModel("李四", 21);UserModel userModel3 = new UserModel("王五", 26);UserModel userModel4 = new UserModel("赵六", 20);UserModel userModel5 = new UserModel("钱七", 28);return new List<UserModel> {  userModel1, userModel2, userModel3, userModel4, userModel5 };}//实现比较器int Compare(UserModel obj1, UserModel obj2){if (obj1.Age>obj2.Age){return 1;}if (obj1.Age == obj2.Age){return 0;}if (obj1.Age < obj2.Age){return -1;}return 0;}private void Print(List<UserModel> userModels){if (userModels == null || userModels.Count < 1) return;foreach (var userModel in userModels){Console.WriteLine(userModel);}}}//Class_end
}

  2.6.3、客户端测试


namespace TemplateMethodPattern
{internal class Program{static void Main(string[] args){ListSortTest();Console.ReadLine();}/// <summary>/// 列表排序测试/// </summary>private static void ListSortTest(){Console.WriteLine("------列表排序测试------");TemplateMethodSort.ListSort listSort= new TemplateMethodSort.ListSort();listSort.SortTest();}}//Class_end
}

  2.6.4、运行结果

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPatterninternal 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/internalprivate 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/privateprotected 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/protectedpublic 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/publicprotected internal 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/protected-internalprivate internal 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/private-protectedabstract 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/abstractvirtual 关键字 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/virtualsealed 修饰符 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/sealed

http://www.xdnf.cn/news/1071793.html

相关文章:

  • SpringBoot 防刷 重复提交问题 重复点击问题 注解 RequestParam RequestBody
  • clion与keil分别配置项目宏定义
  • Python打卡:Day39
  • MySQL 连接指定端口后,为什么实际仍是 3306?
  • 什么是故障注入测试
  • 智能助手(利用GPT搭建智能系统)
  • 性能测试常见指标与瓶颈分析方法
  • 利用python实现NBA数据可视化
  • Python Selenium 滚动到特定元素
  • 10【认识文件系统】
  • 视觉疲劳检测如何优化智能驾驶的险情管理
  • 【RAG面试题】LLMs已经具备了较强能力,存在哪些不足点?
  • 【k近邻】 K-Nearest Neighbors算法原理及流程
  • 《高等数学》(同济大学·第7版)第九章 多元函数微分法及其应用第五节多元函数微分学的几何应用
  • 桌面小屏幕实战课程:DesktopScreen 13 HTTP SERVER
  • [Python]-基础篇1- 从零开始的Python入门指南
  • Python打卡:Day38
  • .NetCore+Vue快速生产框架开发详细方案
  • 深入解析RNN模型:应用、结构与构建实战
  • C++ 第三阶段 并发与异步 - 第二节:异步任务(std::async)
  • 深度拆解Deep Research系统架构与路线图
  • MySQL在C中常用的API接口
  • Linux信号机制:从入门到精通
  • Java项目:基于SSM框架实现的宠物综合服务平台管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】
  • 【记录】Ubuntu|Ubuntu服务器挂载新的硬盘的流程(开机自动挂载)
  • 动手学Python:从零开始构建一个“文字冒险游戏”
  • 2025.6.27总结
  • react-sequence-diagram时序图组件
  • 消息队列的网络模型详解:IO多路复用、Reactor模型、零拷贝
  • 将ONNX模型转换为(OPENMV可用的格式)TensorFlow Lite格式