什么是模板模式?
模板方法(Template Method)模式的定义如下:定义一个主线流程业务逻辑(abstract类),将共用的方法在父类中实现,不共用的方法定义为抽象方法(abstract方法),在不同子类中进行实现。从而达到子类可以不改变该父类方法主流程的情况下重定义某些特定步骤。它是一种类行为型模式。
主要优点如下:
- 它封装了共用部分,扩展了可变部分。共用方法父类实现,不共用方法定义抽象方法,便于子类继续扩展。
- 在父类中抽离了共用部分,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
主要缺点如下:
- 每次新增特殊业务,都需要new一个子类继承,会导致复杂度变高,设计变抽象,增加了系统复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构 (钩子-尽量少用,不然交接给后人会挨骂!-后面会讲),它提高了代码逻辑的复杂度,代码可读性难度增加。
- 这个就是abstract本身的痛点!父类添加新的抽象方法,则所有子类都要重改一遍!
注意!注意!注意!前方高能,实用场景来了!干货。
收银台模板模式
1、第一步 定义一个模板类(abstract)
/**
* @Author 孙帅哥
**/
abstract class ShouyinTaiTemplate {
public static void main(String[] args) {
//使用京东支付方式
ShouyinTaiTemplate shouyinTaiTemplate = new JdPay();
shouyinTaiTemplate.templateMethod();
//使用微信支付方式
ShouyinTaiTemplate shouyinTaiTemplate1 = new WeCartPay();
shouyinTaiTemplate1.templateMethod();
}
public void templateMethod() {
getAuthor();//获取凭证
buildingMoney();//组装金额、秘钥
connectRpc();//调用三方接口
doPay();//执行扣减逻辑
}
//获取凭证
private void getAuthor() {
System.out.println("获取公司账户支付秘钥:gongxifacai12345 ");
}
//组装金额、秘钥
private void buildingMoney() {
System.out.println("组装秘钥:gongxifacai12345,支付金额:100万");
}
//调用三方接口(抽象类-供子类重写:比如 微信支付接口、京东支付接口)
public abstract void connectRpc();
//执行扣减逻辑
private void doPay() {
System.out.println(" 支付成功-支付金额:100万");
}
}
2、第二步 创建两个支付通道类 继承模板
微信支付子类:
/**
* @Author 大帅哥
**/
public class WeCartPay extends ShouyinTaiTemplate{
@Override
public void connectRpc() {
System.out.println("调用微信支付接口成功!");
}
}
京东支付子类:
/**
* @Author 大帅哥
**/
public class JdPay extends ShouyinTaiTemplate {
@Override
public void connectRpc() {
System.out.println("调用京东支付接口成功!");
}
}
调用结果如下:
通过结果可以看到 父类中封装了支付前、支付后的所有操作,在调用支付接口的时候选择了两个子类去实现具体的rpc接口规则。
通过上述例子,我们来分析一下,一个模板模式的结构组成吧!
模板方法模式包含以下主要角色
1、抽象类/抽象模板(Abstract Class)
抽象模板类,负责定义主线业务流程方法。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
(1)模板方法:定义主流程方法,以业务方法顺序包含的基本方法。
(2)基本方法:共用基本方法、抽象基本方法、钩子方法。
-
抽象方法:在抽象类中声明,由具体子类实现。
-
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
-
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2、具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
钩子方法(少用!少用!少用!会增加复杂度)
还是刚才的收银台demo
1、定义模板类
/**
* @Author 帅哥
**/
abstract class ShouyinTaiTemplate {
public static void main(String[] args) {
// //使用京东支付方式
// ShouyinTaiTemplate shouyinTaiTemplate = new JdPay();
// shouyinTaiTemplate.templateMethod();
//使用微信支付方式
ShouyinTaiTemplate shouyinTaiTemplate1 = new WeCartPay();
shouyinTaiTemplate1.templateMethod();
}
public void templateMethod() {
getAuthor();//获取凭证
buildingMoney();//组装金额、秘钥
//钩子逻辑
if(!moneyMaxHook()){
//如果支付渠道金额红线 则退出支付流程
System.out.println("支付流程中断!");
return;
}
connectRpc();//调用三方接口
//钩子特殊逻辑处理
specialHook();
doPay();//执行扣减逻辑
}
//定义钩子方法-类型1-控制父类方法(本质也是子类重写父类方法)-少用
public Boolean moneyMaxHook() {
return true;
}
//定义钩子方法-类型2-重写父类方法(父类定义空方法 供子类实现重写逻辑)-少用
public void specialHook() {
}
//获取凭证
private void getAuthor() {
System.out.println("获取公司账户支付秘钥:gongxifacai12345 ");
}
//组装金额、秘钥
private void buildingMoney() {
System.out.println("组装秘钥:gongxifacai12345,支付金额:100万");
}
//调用三方接口(抽象类-供子类重写:比如 微信支付接口、京东支付接口)
public abstract void connectRpc();
//执行扣减逻辑
private void doPay() {
System.out.println(" 支付成功-支付金额:100万");
}
}
2、定义两个支付方式子类:
京东支付子类:
/**
* @Author 大帅哥
**/
public class JdPay extends ShouyinTaiTemplate {
@Override
public void connectRpc() {
System.out.println("调用京东支付接口成功!");
}
@Override
public Boolean moneyMaxHook() {
System.out.println("因为金额达到100W,触发红线逻辑!阻断支付");
return false;
}
}
微信支付子类:
/**
* @Author 大帅哥
**/
public class WeCartPay extends ShouyinTaiTemplate{
@Override
public void connectRpc() {
System.out.println("调用微信支付接口成功!");
}
@Override
public void specialHook() {
System.out.println("微信支付触发特殊逻辑!惊喜大礼包!");
}
}
我们先看子类重写父类原有的值的钩子结果返回:
再看子类重写父类空方法的钩子结果返回:
总结:
- 模板模式的使用场景,尽量贴近与这种子类比较少的场景。
- 尽量少用钩子方法,后期的维护会非常的困难。