模板方法模式
模拟场景:
登录控制:
现在有一个基于Web的企业级应用系统,需要实现两种登录控制(管理员登录和客户登录),直接使用不同的登录页面来区分它们。
下面是基本的功能需求描述:
普通客户登录前台的登录控制功能:
(1)前台界面:用户能输入用户名和密码;提交登录请求,让系统进行登录控制。
(2)后台:从数据库获取登录人员的信息。
(3)后台:判断从前台传递过来的登录数据和数据库中已有的数据是否匹配。
(4)前台Action:如果匹配就转向首页,如果不匹配就返回到登录页面,并显示错误提示信息。
管理员登录后台的登录控制功能:
(1)前台界面:用户能输入用户名和密码;提交登录请求,让系统进行登录控制。
(2)后台:从数据库获取登录人员的信息。
(3)后台:把从前台传递过来的密码数据使用相应的加密算法进行加密运算,得到加密后的密码数据。
(4)后台:判断从前台传递过来的用户名和加密后的密码数据和数据库中已有的数据是否匹配。
(5)前台Action:如果匹配就转向首页,如果不匹配就返回到登录界面,并显示错误提示信息。
-----------------------------------------------------------------------------------------------
首先不用模式的解决方案:
将普通用户登录和工作人员登录完全作为两个独立的小模块来完成:
下面是普通客户登录代码演示:
public classNormalLogin
{/*** 功能:
* 判断登录数据是否正确,也就是是否能登录成功
*@paramlm 封装登录数据的Model
*@returntrue表示登录成功,false表示登录失败*/
public booleanlogin(UserLoginModel lm)
{//从数据库获取登录人员的信息,就是根据用户编号去获取人员的数据
UserModel um = this.findUserById(lm.getUserId());//判断从前台传递过来的登录数据和数据库中已有的数据是否匹配//先判断用户是否存在,如果um为null,说明用户肯定不存在//但是不为null,用户不一定存在,因为数据层可能返回new UserModel();//因此还需要做进一步的判断
if(um != null)
{//如果用户存在,检查用户编号和密码是否匹配
if(um.getUserId().equals(lm.getUserId()) &&um.getPwd().equals(lm.getPwd()))
{return true;
}
}return false;
}/***@paramuserId 用户编号
*@return对应的用户的详细信息*/
privateUserModel findUserById(String userId)
{//这里省略具体的处理,仅做示意,返回一个有默认数据的对象
UserModel um = newUserModel();
um.setUserId(userId);
um.setName("test");
um.setPwd("test");
um.setUuid("User001");returnum;
}
}
/*** 功能:
* 描述用户信息的数据模型
*@authorAdministrator
**/
public classUserModel
{privateString uuid;privateString userId;privateString pwd;privateString name;publicString getUuid()
{returnuuid;
}public voidsetUuid(String uuid)
{this.uuid =uuid;
}publicString getUserId()
{returnuserId;
}public voidsetUserId(String userId)
{this.userId =userId;
}publicString getPwd()
{returnpwd;
}public voidsetPwd(String pwd)
{this.pwd =pwd;
}publicString getName()
{returnname;
}public voidsetName(String name)
{this.name =name;
}
}
/*** 功能:
* 描述登录人员登录时填写的信息的数据模型
*@authorAdministrator
**/
public classUserLoginModel
{privateString userId;privateString pwd;publicString getUserId()
{returnuserId;
}public voidsetUserId(String userId)
{this.userId =userId;
}publicString getPwd()
{returnpwd;
}public voidsetPwd(String pwd)
{this.pwd =pwd;
}
}
下面是管理员登录代码演示:
public classWorkerLogin
{/*** 功能:
* 判断登录数据是否正确,也就是是否能登录成功
*@paramlm 封装登录数据的Model
*@returntrue表示登录成功,false表示登录失败*/
public booleanlogin(WorkerLoginModel lm)
{//根据工作人员编号去获取工作人员的数据
WorkerModel wm =findWorkerByWorkerId(lm.getWorkerId());//判断从前台传递过来的用户名和加密后的密码数据//和数据库中已有的数据是否匹配//先判断工作人员是否存在,如果wm为null,说明工作人员肯定不存在//但是不为Null,工作人员也不一定存在//因为数据层可能返回new WorkerModel();因此还需要进一步判断
if(wm != null)
{//把前台传来的密码数据使用相应的加密算法进行加密运算
String encryptPwd = this.encryptPwd(lm.getPwd());//如果工作人员存在,检查工作人员的编号和密码是否匹配
if(wm.getWorkerId().equals(lm.getWorkerId()) &&wm.getPwd().equals(encryptPwd))
{return true;
}
}return false;
}/*** 功能:
* 对密码数据进行加密
*@parampwd
*@return
*/
privateString encryptPwd(String pwd)
{//这里对密码数据进行加密
returnpwd;
}/*** 功能:
* 根据工作人员编号获取工作人员的详细信息
*@paramworkerId
*@return
*/
privateWorkerModel findWorkerByWorkerId(String workerId)
{
WorkerModel wm= newWorkerModel();
wm.setWorkerId(workerId);
wm.setName("Worker1");
wm.setPwd("Worker1");
wm.setUuid("Worker0001");returnwm;
}
}
/*** 功能:
* 描述登录人员登录时填写的信息的数据模型
*@authorAdministrator
**/
public classWorkerLoginModel
{privateString workerId;privateString pwd;publicString getWorkerId()
{returnworkerId;
}public voidsetWorkerId(String workerId)
{this.workerId =workerId;
}publicString getPwd()
{returnpwd;
}public voidsetPwd(String pwd)
{this.pwd =pwd;
}
}
/*** 功能:
* 描述工作人员信息的数据模型
*@authorAdministrator
**/
public classWorkerModel
{privateString uuid;privateString workerId;privateString pwd;privateString name;publicString getUuid()
{returnuuid;
}public voidsetUuid(String uuid)
{this.uuid =uuid;
}publicString getWorkerId()
{returnworkerId;
}public voidsetWorkerId(String workerId)
{this.workerId =workerId;
}publicString getPwd()
{returnpwd;
}public voidsetPwd(String pwd)
{this.pwd =pwd;
}publicString getName()
{returnname;
}public voidsetName(String name)
{this.name =name;
}
}
上面的实现,有两个很明显的问题:一是重复或相似代码太多;二是扩展起来很不方便。
---------------------------------------------------------------------------------------
下面使用模板方法模式来解决上诉的问题
首先介绍一些关于模板方法模式的概念:
模板方法模式的定义:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
下面分析一下,登录控制大致的逻辑判断步骤如下:
(1)根据登录人员的编号去获取相应的数据。
(2)获取对登录人员填写的密码数据进行加密后的数据,如果不需要加密,那就直接返回登录人员填写的密码数据。
(3)判断登录人员填写的数据和从数据库中获取的数据是否匹配。
在上面的3个步骤中,第一个和第三个步骤时必不可少的,而第二个步骤是可选的。那么就可以定义一个父类,在其中定义一个方法来定义这个算法骨架,这个方法就是模板方法,然后把父类无法确定的实现,延迟到具体的子类来实现就可以了。
下面是模板方法模式的通用结构和说明:
(1)AbstractClass:抽象类。用来定义算法骨架和原语操作,具体的子类通过重定义这些原语操作来实现一个算法的各个buz。
(2)ConcreteClass:具体的实现类。用来实现算法骨架中的某些步骤,完成与特定子类相关的功能。
下面看看模板方法模式的通用演示代码:
/*** 功能:
* 定义模板方法、原语操作等抽象类
*@authorAdministrator
**/
public abstract classAbstractClass
{/*** 功能:
* 原语操作1,所谓原语操作就是抽象的操作,必须要由子类提供实现*/
public abstract voiddoPrimitiveOperation1();/*** 功能:
* 原语操作2,所谓原语操作就是抽象的操作,必须要由子类提供实现*/
public abstract voiddoPrimitiveOperation2();/*** 功能:
* 模板方法,定义算法骨架*/
public final voidtemplateMethod()
{
doPrimitiveOperation1();
doPrimitiveOperation2();
}
}
/*** 功能:
* 具体实现类,实现原语操作
*@authorAdministrator
**/
public class ConcreteClass extendsAbstractClass
{
@Overridepublic voiddoPrimitiveOperation1()
{//具体的实现
}
@Overridepublic voiddoPrimitiveOperation2()
{//具体的实现
}
}
-----------------------------------------------------------------------------------------
下面使用模板方法模式重写登录示例:
下面的UML图显示了系统的结构:
下面进行代码演示:
/*** 功能:
* 封装进行登录控制所需要的数据
*@authorAdministrator
**/
public classLoginModel
{//登录人员的编号,通用的,可能是用户编号,也可能是工作人员编号
privateString loginId;//登录的密码
privateString pwd;publicString getLoginId()
{returnloginId;
}public voidsetLoginId(String loginId)
{this.loginId =loginId;
}publicString getPwd()
{returnpwd;
}public voidsetPwd(String pwd)
{this.pwd =pwd;
}
}
/*** 功能:
* 登录控制的模板
*@authorAdministrator
**/
public abstract classLoginTemplate
{/*** 功能:判断登录数据是否正确,也就是是否登录成功
*
*@paramlm 封装登录数据的Model
*@returntrue 表示登录成功,false表示登录失败*/
public final booleanlogin(LoginModel lm)
{//根据登录人员的编号去获取相应的数据
LoginModel dbLm = this.findLoginUser(lm.getLoginId());if(dbLm != null)
{//对密码进行加密
String encryptPwd = this.encryptPwd(lm.getPwd());//把加密后的密码设置回到登录数据模型中
lm.setPwd(encryptPwd);return this.match(lm,dbLm);
}return false;
}/*** 功能:
* 根据登录编号来查找和获取存储中相应的数据
*@paramloginId 登录编号
*@return登录编号在存储中相对应的数据*/
public abstractLoginModel findLoginUser(String loginId);/*** 功能:
* 对密码数据进行加密
*@parampwd
*@return
*/
publicString encryptPwd(String pwd)
{returnpwd;
}/*** 功能:
* 判断用户填写的数据和存储中对应的数据是否匹配得上
*@paramlm 用户填写的登录数据
*@paramdbLm 在存储中对应的数据
*@returntrue表示匹配成功,false表示匹配失败*/
public booleanmatch(LoginModel lm,LoginModel dbLm)
{if(lm.getLoginId().equals(dbLm.getLoginId())&&lm.getPwd().equals(dbLm.getPwd()))
{return true;
}return false;
}
}
/*** 功能:
* 普通用户登录控制的逻辑处理
*@authorAdministrator
**/
public class NormalLogin extendsLoginTemplate
{
@OverridepublicLoginModel findLoginUser(String loginId)
{//这里省略具体的处理,仅做示意,返回一个有默认数据的对象
LoginModel lm = newLoginModel();
lm.setLoginId(loginId);
lm.setPwd("testpwd");returnlm;
}
}
/*** 功能:
* 工作人员登录控制的逻辑处理
*@authorAdministrator
**/
public class WorkerLogin extendsLoginTemplate
{
@OverridepublicLoginModel findLoginUser(String loginId)
{//这里省略具体的处理,仅做示意,返回一个有默认数据的对象
LoginModel lm = newLoginModel();
lm.setLoginId(loginId);
lm.setPwd("workerpwd");returnlm;
}
@OverridepublicString encryptPwd(String pwd)
{//覆盖父类的方法,提供真正的加密实现//这里对密码进行加密
System.out.println("使用MD5进行密码加密");returnpwd;
}
}
public classClient
{public static voidmain(String[] args)
{//准备登录人的信息
LoginModel lm = newLoginModel();
lm.setLoginId("admin");
lm.setPwd("workerpwd");//准备用来进行判断的对象
LoginTemplate lt = newWorkerLogin();
LoginTemplate lt2= newNormalLogin();//进行登录测试
boolean flag =lt.login(lm);
System.out.println("可以登录工作平台 = " +flag);boolean flag2 =lt2.login(lm);
System.out.println("可以进行普通员工登录 = " +flag2);
}
}
运行结果:
使用MD5进行密码加密
可以登录工作平台= true可以进行普通员工登录= false
---------------------------------------------------------------------------------------根据上面的代码示例:对模板方法模式进行讲解
(1)模板方法模式的功能:
摸办方法模式的功能在于固定算法骨架,而让具体算法实现可扩展。而且可以控制子类的扩展,因为在父类中定义好了算法的步骤,只是在某几个固定的点才会调用到北子类实现的方法,因此就业只有允许在这几个点来扩展功能。
(2) 通常在模板里面包含以下操作类型:
a.模板方法:就是定义算法骨架的方法。
b.具体的操作:在模板中直接实现某些步骤的方法。(通常这些步骤的实现算法是固定的,而且不怎么变化的,因此可以将其当作公共功能实现在模板中。)
c.具体的AbstractClass操作:在模板中实现某些公共功能,可以提供给子类使用,一般不是具体的算法步骤的实现,而是一些辅助的公共功能。
d.原语操作:就是在模板中定义的抽象操作,通常是模板方法需要调用的操作,是必须的操作,而且在父类中还没有办法确定下来如何实现,需要子类来真正实现的方法。
e.钩子操作:在模板中定义,并提供默认实现的操作。这些方法通常被视为可扩展的点,但不是必须的,子类可以选择的覆盖这些方法,以提供新的实现来扩展功能。
f.Factory Method:在模板方法中,如果需要得到某些对象的实例的话,可以考虑通过工厂方法模式来获取,把具体的构造对象的实现延迟到子类中去。
(3)下面总结一下,一个较为完整的模板定义实例:
/*** 功能:
* 一个较为完整的模板定义示例
*@authorAdministrator
**/
public abstract classAbstractTemplate
{/*** 功能:
* 模板方法,定义算法骨架*/
public final voidtemplateMethod()
{//第一步
this.operation1();//第二步
this.operation2();//第三步
this.doPrimitiveOperation1();//第四步
this.doPrimitiveOperation2();//第五步:
this.hookOperation1();
}/*** 功能:
* 具体操作1,算法中的步骤,固定实现,而且子类不需要访问*/
private voidoperation1()
{//这里具体的实现
}/*** 功能:
* 具体操作2,算法中的步骤,固定实现,子类可能需要访问
* 当然也可以定义成protected的,不可以被覆盖,因此是final的*/
protected final voidoperation2()
{//这里是具体的实现
}/*** 功能:
* 具体的AbstractClass操作,子类的公共功能
* 但通常不是具体的算法步骤*/
protected voidcommOperation()
{//这里是具体的实现
}/*** 功能:
* 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现*/
protected abstract voiddoPrimitiveOperation1();/*** 功能:
* 原语操作2,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现*/
protected abstract voiddoPrimitiveOperation2();/*** 功能:
* 钩子操作,算法中的步骤,不一定需要,提供默认实现,
* 由子类选择并具体实现*/
protected voidhookOperation1()
{//这里是具体的实现
}/*** 功能:
* 工厂方法,创建某个对象,这里用Object代替了,在算法实现中可能需要
*@return创建的某个算法实现需要的对象*/
protected abstractObject createOneObject();
}
------------------------------------------------------------------------------------------
Java回调与模板方法模式
(1)模板方法模式的一个目的,就在于让其他类来扩展或具体实现在模板中固定的算法骨架中的某些算法步骤。在标准的模板方法模式实现中,主要使用继承的方法,来让父类在运行期间可以调用到子类的方法。-----Java回调技术也可以实现相同的功能。
(2)使用Java回调来实现模板方法模式,在实际开发中使用的也非常多,也算是模板方法模式的一种变形实现。
下面采用Java回调来实现前面的登录示例:
先定义一个模板方法需要的回调接口
/*** 功能:
* 登录控制的模板方法需要的回调接口,需要把所有需要的接口方法都定义出来或者
* 说是所有可以被扩展的方法都需要被定义出来
*@authorAdministrator
**/
public interfaceLoginCallback
{/*** 功能:
* 根据登录编号来查找和获取存储中相应的数据
*@paramloginId 登录编号
*@return登录编号在存储中相对应的数据*/
publicLoginModel findLoginUser(String loginId);/*** 功能:
* 对密码数据进行加密
*@parampwd 密码数据
*@paramtemplate LoginTemplate对象,通过它来调用在LoginTemplate中定义的公共方法或默认实现
*@return加密后的密码数据*/
publicString encryptPwd(String pwd,LoginTemplate template);/*** 功能:
* 判断用户填写的登录数据和存储中对应的数据是否匹配的上
*@paramlm 用户填写的登录数据
*@paramdbLm 在存储中对应的数据
*@paramtemplate LoginTemplate对象,通过它来调用在LoginTemplate中定义的公共方法或默认实现
*@returntrue表示匹配成功,false表示匹配失败*/
public booleanmathc(LoginModel lm,LoginModel dbLm,LoginTemplate template);
}
下面定义登录控制的模板:
/*** 功能:
* 登录控制的模板
*@authorAdministrator
**/
public classLoginTemplate
{/*** 功能:判断登录数据是否正确,也就是是否登录成功
*
*@paramlm 封装登录数据的Model
*@paramcallback LoginCallback对象
*@returntrue 表示登录成功,false表示登录失败*/
public final booleanlogin(LoginModel lm,LoginCallback callback)
{//根据登录人员的编号去获取相应的数据
LoginModel dbLm =callback.findLoginUser(lm.getLoginId());if(dbLm != null)
{//对密码进行加密
String encryptPwd = callback.encryptPwd(lm.getPwd(),this);//把加密后的密码设置回到登录数据模型中
lm.setPwd(encryptPwd);return callback.match(lm,dbLm,this);
}return false;
}/*** 功能:
* 对密码数据进行加密
*@parampwd 密码数据
*@return加密后的密码数据*/
publicString encryptPwd(String pwd)
{returnpwd;
}/*** 功能:
* 判断用户填写的数据和存储中对应的数据是否匹配得上
*@paramlm 用户填写的登录数据
*@paramdbLm 在存储中对应的数据
*@returntrue表示匹配成功,false表示匹配失败*/
public booleanmatch(LoginModel lm,LoginModel dbLm)
{if(lm.getLoginId().equals(dbLm.getLoginId())&&lm.getPwd().equals(dbLm.getPwd()))
{return true;
}return false;
}
}
客户端:
public classClient
{public static voidmain(String[] args)
{//准备登录人的信息
LoginModel lm = newLoginModel();
lm.setLoginId("admin");
lm.setPwd("workerpwd");//准备用来进行判断的对象
LoginTemplate lt = newLoginTemplate();//准备登录测试,先测试普通人员登录
boolean flag = lt.login(lm, newLoginCallback(){
@OverridepublicString encryptPwd(String pwd, LoginTemplate template)
{//自己不需要实现这个功能,直接转调模板中的默认实现
returntemplate.encryptPwd(pwd);
}
@OverridepublicLoginModel findLoginUser(String loginId)
{//这里省略具体的处理,仅做示意,返回一个有默认数据的对象
LoginModel lm = newLoginModel();
lm.setLoginId(loginId);
lm.setPwd("testPwd");returnlm;
}
@Overridepublic booleanmatch(LoginModel lm, LoginModel dbLm, LoginTemplate template)
{//自己不需要覆盖,直接调转模板中的默认方法
returntemplate.match(lm, dbLm);
}
});
System.out.println("可以进行普通人员登录 = " +flag);//测试工作人员登录
boolean flag2 = lt.login(lm, newLoginCallback(){
@OverridepublicString encryptPwd(String pwd, LoginTemplate template)
{//覆盖父类的方法,提供真正的加密实现//这里对密码进行加密,比如示意MD5,3DES等
System.out.println("使用MD5进行密码加密");returnpwd;
}
@OverridepublicLoginModel findLoginUser(String loginId)
{//这里省略具体的处理,仅做示意,返回一个有默认数据的对象
LoginModel lm = newLoginModel();
lm.setLoginId(loginId);
lm.setPwd("workerpwd");returnlm;
}
@Overridepublic booleanmatch(LoginModel lm, LoginModel dbLm, LoginTemplate template)
{//自己不需要覆盖,直接调转模板中的默认方法
returntemplate.match(lm, dbLm);
}
});
System.out.println("可以进行管理人员登录 = " +flag2);
}
}
运行结果:
可以进行普通人员登录 = false使用MD5进行密码加密
可以进行管理人员登录= true
下面简单对模板方法模式的两种实现方法进行一个小结:
(1)使用继承的方法,抽象方法和具体实现的关系是在编译期间静态决定的,是类级的关系;使用Java回调,这个关系是在运行期间动态决定的,是对象级的关系。
(2)相对而言,使用继承方式会更简单点,因为父类提供了实现的方法,子类如果不想扩展,那就不用管。如果使用回调机制,回调的接口需要把所有可能被扩展的方法都定义进去,这就导致实现的时候,不管你要不要扩展,都要实现这个方法,哪怕你什么都不做,只是转调模板中已有的实现,都要写 出来。
(3)相对而言,使用回调机制会更灵活,因为Java是单继承的,如果使用继承的方法,对于子类而言,今后就不能继承其他对象了,而使用回调,是基于接口的。