定义:
定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构既可重定义该算法的某些特定步骤。
强调:这里面的重定义既可以是延迟到子类必须实现的方法,也可以是父类中可以被重写的方法。
Paste_Image.png正如上图中,算法骨架中operation3与operation4都是要求子类必须去实现的,但是如果用户需要的话,也可以重定义operation2,operation5等。
Father:抽象类,用来定义算法的骨架和原语操作,在这个类里面还可以提供算法中通用的实现。
Son:具体实现类。用来实现算法骨架中的某些特定步骤,完成跟特定子类相关的功能。
业务
不同角色的用户登录功能,在购物类网站开发中,经常会出现一个网站分为前台和后台(这里的后台主要是管理员或者提供商,不是服务端)的功能,但是都有一个共同点,那就是登录功能。再仔细的区分一下会发现,这些登录操作其实是拥有不同权限、不同角色的用户登录。
登录逻辑大致分为以下几个步骤:
1、 前台(客户端):用户输入用户名、密码、验证码等信息,提交请求
2、 后台(服务端):获取用户输入信息(对密码进行相应的操作),操作数据库
3、 后台(服务端):判断从前台传递过来的数据,是否和数据库中的记录匹配
4、 如果匹配从数据库中获取一条或几条用户信息,剪辑数据,返回给前台有用的信息。如果不匹配返回前台显示错误信息错误信息。
当前角色为:普通用户、供应商用户
登录流程图如下:
Paste_Image.png不用模板的实现与使用模板的实现流程图如下
Paste_Image.png在未使用模板方法的流程图中可以看出:
1、不同用户登录流程大体相同
2、重复或者相似的代码太多
3、如果后期业务修改登录这块的业务,可能就需要修改两个部分,如果再添加几个不同权限的用户,可能需要更多相似的代码。
这个时候就要考虑使用模板方法设计模式了。
无模板方法模式的示例代码:
普通用户登录
package templatemethod.notemplate;
/**
* 普通用户登录Login
*/
public class UserLogin {
public boolean login(LoginModel loginModel){
//对用户传入的passworld进行加密,然后拿着用户名,加密密后的密码,用户类型查找用户
loginModel.setPassword(encryptPwd(loginModel.getPassword()));
UserModel userModel = findUserByInfo(loginModel);
System.out.println(userModel);
if(userModel!=null)
return true;
else
return false;
}
public UserModel findUserByInfo(LoginModel loginModel) {
//如果查找到用户就返回当前用户,此处模拟为查找到用户
return new UserModel("1","张飞","1994-05-21");
}
public String encryptPwd(String password) {
return password +"_base64";
}
}
供应商用户登录
package templatemethod.notemplate;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 供应商用户登录
*/
public class SupplierLogin {
public boolean login(LoginModel loginModel){
SupplierModel supplierModel = findUserByInfo(loginModel);
System.out.println(supplierModel);
if(supplierModel!=null)
return true;
else
return false;
}
public SupplierModel findUserByInfo(LoginModel loginModel) {
//对用户传入的passworld进行加密,然后拿着用户名,加密密后的密码,用户类型查找用户
//如果查找到用户就返回当前用户,此处模拟为查找到用户
String encryptPwd = encryptPwd(loginModel.getPassword());
return new SupplierModel("2","种花家公司","丝绸,陶瓷");
}
/**
* 对字符串进行MD5加密
* @param password
* @return
*/
public String encryptPwd(String password) {
try {
MessageDigest md5=MessageDigest.getInstance("MD5");
return new String(md5.digest(password.getBytes("utf-8")));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return password;
}
}
执行流程几乎相同。如果在流程中需要修改其它的操作,比如把操作步骤换一下位置,那么得同时修改两份代码。而且在代码结构上,用了相同的方法,造成代码的重复。
使用模板设计模式修改
package templatemethod.notemplate;
/**
* 模板方法设计模式
*/
public abstract class LoginTemplate<T> {
/**
* 定义算法的骨架,用final修饰不能修改
* @param loginModel
* @return
*/
public final boolean login(LoginModel loginModel){
//1、对获取到的密码进行加密
String encryptPwd = encryptPwd(loginModel.getPassword());
loginModel.setPassword(encryptPwd);
//2、从数据库中查找数据
T resultModel = findUserByInfo(loginModel);
System.out.println(resultModel);
if(resultModel!=null)
return true;
else
return false;
}
/**
* 原语操作,子类必须要实现,因为查询不同的数据,涉及到的表可能也不相同,结果也会不同
* @param loginModel
* @return
*/
public abstract T findUserByInfo(LoginModel loginModel);
/**
* 钩子操作,提供默认实现,子类可以对该方法进行重写
* @param password
* @return
*/
public String encryptPwd(String password) {
return password +"_base64";
}
}
其子类实现
package templatemethod.notemplate;
/**
* 普通用户登录
*/
public class UserLogin2 extends LoginTemplate<UserModel> {
@Override
public UserModel findUserByInfo(LoginModel loginModel) {
return new UserModel("1","张飞","1994-05-21");
}
}
package templatemethod.notemplate;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 供应商用户登录
*/
public class SupplierLogin2 extends LoginTemplate<SupplierModel> {
@Override
public SupplierModel findUserByInfo(LoginModel loginModel) {
return new SupplierModel("2","种花家公司","丝绸,陶瓷");
}
/**
* 不同用户涉及到的密码保存方式不同,Supplier用户需要进行MD5
*/
@Override
public String encryptPwd(String password) {
try {
MessageDigest md5=MessageDigest.getInstance("MD5");
return new String(md5.digest(password.getBytes("utf-8")));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return password;
}
}
从代码示例中可以看出,我们将算法估计抽离出来(login方法中的登录步骤),而不同角色的用户登录,获取用户信息的方式不同(这些操作被延迟到子类中去实现)
模板实现类图
Paste_Image.png模式的功能:
模板方法的功能在于固定算法的骨架,而让具体的实现可以延迟到子类中进行扩展。比如在DAO的实现中,设计通用的增删该查功能,然后开发者只需要在实现的子类中处理特定的方法,就可以实现不同的操作。
模板方法可以控制子类的扩展。因为在父类中已经定义好算法的骨架,只是在几个固定的点才会调用到被子类实现的方法,因此也就只允许在这几个点来扩展功能,这些个可以被子类扩展和覆盖的方法称为钩子方法。
钩子方法:就是可以在父类中提供默认的实现,如果有必要,可以再子类的重写该方法。和模板方法还不太一样(模板方式在子类中是必须要实现的方法)。
模板方法与实现接口的区别
抽象类有这样一个作用:约束子类行为,为子类提供公共方法。
而模板方法模式中需要固定定义算法的骨架,而这个骨架在子类中是不能够重写的,里面具体的步骤的实现又可能是各不相同的,恰好符合选择抽象类的原则。这里面的具体步骤在子类中去实现,约束了子类的行为。
至于为什么不选择接口,因为接口根本就没法固定算法的骨架,执行流程没法控制。
设计理念
变与不变!
分析程序中哪些功能是可变的,哪些功能是不变的,把不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或用抽象类来约束子类型为。
模板方法中,不变的部分是算法骨架(各种方法的执行顺序),变化的部分是抽象出去被子类实现或者重写的方法。
业务拓展
1、 不修改算法骨架,此时只需要提供新的子类即可
2、 算法骨架中需要添加新的功能,修改骨架。
3、 既要添加新的功能,又要实现新的子类。此时考虑修改骨架
模板方法结构
1、 模板方法:定义算法骨架的方法
2、 具体操作:某些步骤的实现方法是固定不变的,直接在模板中定义实现。如果子类需要可以提供给子类protected final 修饰,不需要的话直接private修改即可
3、 普通方法:跟算法骨架没多大关系,辅助功能
4、 原语操作:模板中定义的抽象操作,是骨架中需要实现的操作,且在父类中没法确定,需要子类去实现
5、 钩子操作:在模板中提供默认实现操作。通常被视为可以拓展的功能,但是这个扩展是非必须的,子类有选择的去覆盖它。
6、 Factory method:获取子对象的时候,可以考虑使用工厂方法模式来获取,其具体的实现延迟到子类中去操作。
package templatemethod.notemplate;
/**
* 复杂模板方法结构
*/
public abstract class AbstractTemplate {
//模板方法:定义算法的骨架
public final void templateMethod(){
//具体操作
operation();
//原语操作
primitiveOperation1();
primitiveOperation2();
//钩子操作
hookOperation();
}
/**
* 具体操作,如果使用private修饰不让子类访问,如果子类想要访问,可以使用public final或者protected final修饰,保证子类修改不了
*/
private void operation(){
}
/**
* 普通方法,不是算法骨架中的特定步骤
*/
protected void commonOperation(){
}
/**
* 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
*/
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
/**
* 钩子操作,算法中的步骤,提供缺省实现,子类选择是否去实现
*/
protected void hookOperation(){
//在这里提供缺省的实现
}
/**
* 工厂方法,主要是用来保护子类对象的创建过程
* @return 创建的某个算法实现需要的对象
*/
protected abstract<T> T createOneObject();
}
//createOneObject这个比较有意思
如果是简单的模板方法,只需要提供算法骨架和原语操作即可。
API中使用模板
Collections.sort()
InputStream.read()
可参考连接:
Java中模板方法和钩子的使用示例
总结:模板方法的本质和优缺点
模板方法的本质:固定算法骨架
优点:
1、模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码,实现代码复用
2、子类实现算法的某些细节,有助于算法的扩展
3、通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
缺点:
1、 算法骨架不易升级
2、 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象
网友评论