3.1.2 模板方法模式
具体场景
在创建不同类型运营活动策略的时候,可以发现除了保存具体活动渠道配置信息不一样之外,创建过程中很多操作流程是相同的:比如保存活动基本配置信息,审计日志上报,创建活动审批工单,创建完成后消息提醒等。
原有实践
/** * 短信活动类 * */ @Service public class SmsActivityStrategy{ /** * 执行渠道发送 * * @param msgParam msgParam */ public ProcessResult createActivity(ActParam param) { //保存活动基础信息 saveActBaseConfig(param); //保存短信活动配置 createSmsActivity(param); //审计日志上报 ... //创建活动审批工单 ... //消息通知 ... sendNotification(param); } } /** * Push活动类 * */ @Service public class PushActivityStrategy{ /** * 执行渠道发送 * * @param msgParam msgParam */ public ProcessResult createActivity(ActParam param) { //保存活动基础信息 saveActBaseConfig(param); //保存Push活动配置 createChannelActivity(param); //审计日志上报 ... //创建活动审批工单 ... //消息通知 ... sendNotification(param); } } ...
对于每种活动策略而言,这些操作都是必需的且操作流程都是固定的,所以可以将这些操作提取成公用的流程,此时就考虑到了模板方法模式。
模式分析
在GoF《设计模式:可复用面向对象软件的基础》:模板方法模式是在一个方法中定义一个算法骨架,并将某些步骤推迟到其子类中实现。模板方法模式允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
上面所指的“算法”,可以理解为业务逻辑,而‘’算法骨架“即是模板,包含‘’算法骨架“的方法就是模板方法,这也是模板方法模式名称的来源。
模板方法模式适用场景:业务逻辑由确定的步骤组成,这些步骤的顺序要是固定不变的,不同的具体业务之间某些方法或者实现可以有所不同。
实现时一般通过抽象类来定义一个逻辑模板和框架,然后将无法确定的部分抽象成抽象方法交由子类来实现,调用逻辑仍在抽象类中完成。
典型代码示例
//模板类 public abstract class AbstractTemplate { //业务逻辑1 protected abstract void doStep1(); //业务逻辑2 protected abstract void doStep2(); //模板方法 public void templateMethod(){ this.doStep1(); //公共逻辑 ...... this.doStep2(); } } //具体实现类1 public class ConcreteClass1 extends AbstractTemplate { //实现业务逻辑1 protected void doStep1() { //业务逻辑处理 } //实现业务逻辑2 protected void doStep2() { //业务逻辑处理 } } //具体实现类2 public class ConcreteClass2 extends AbstractTemplate { //实现业务逻辑1 protected void doStep1() { //业务逻辑处理 } //实现业务逻辑2 protected void doStep2() { //业务逻辑处理 } } // 调用类 public class Client { public static void main(String[] args) { AbstractTemplate class1=new ConcreteClass1(); AbstractTemplate class2=new ConcreteClass2(); //调用模板方法 class1.templateMethod(); class2.templateMethod(); } }
实际代码
/** * 活动创建模板类 * * @author chenwangrong */ @Slf4j public abstract class AbstractActivityTemplate{ /** * 保存具体活动配置 * * @param param 活动参数 * @return ProcessResult 处理结果 */ protected abstract ProcessResult createChannelActivity(ActParam param); /** * 执行活动创建 * * @param msgParam msgParam */ public ProcessResult createActivity(ActParam param) { //保存活动基础信息 saveActBaseConfig(param); //保存具体渠道配置 createChannelActivity(param); //审计日志上报 ... //消息通知 ... } } /** * 短信活动类 * */ @Service public class SmsActivityStrategy extends AbstractActivityTemplate{ /** * 创建短信渠道活动配置 * * @param msgParam msgParam */ public ProcessResult createChannelActivity(ActParam param) { //仅需要实现:保存短信活动配置 createSmsActivity(param); } } (其他渠道活动类似,此处省略) // 调用类 public class Client { public static void main(String[] args) { AbstractActivityTemplate smsActivityStrategy=new SmsActivityStrategy(); AbstractActivityTemplate pushActivityStrategy=new PushActivityStrategy(); ActParam param = new ActParam(); //调用具体活动实现类 smsActivityStrategy.createActivity(param); pushActivityStrategy.createActivity(param); } }
实践总结
模板方法模式有两大作用:复用和扩展。复用是指所有的子类可以复用父类中提供的模板方法的代码。扩展是指框架通过模板模式提供功能扩展点,让用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
模板方法非常适用于有通用业务逻辑处理流程,同时又在具体流程上存在一定差异的场景,可以通过将流程骨架抽取到模板类中,将可变的差异点设置为抽象方法,达到封装不变部分,扩展可变部分的目的。
3.1.3 策略模式
具体场景
上述我们通过模板方法模式抽取出了公共流程骨架,但这里还存在一个问题:调用类仍需要明确知道具体实现类是哪个,实例化后才可进行调用。也就是每一次增加新的渠道活动时,调用方都必须修改调用逻辑,添加新的活动实现类的初始化调用,显然不利用业务的扩展性。
在创建运营活动过程中,不同类型的活动会对应着不同的创建流程,调用方只需要根据渠道类型来进行区分,而无需理会其中具体的业务逻辑。此时策略模式是一个比较好的选择。
模式分析
在GoF《设计模式:可复用面向对象软件的基础》中:策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的调用方。
典型代码示例
//策略接口定义 public interface Strategy { void doStrategy(); } //策略具体实现类(多个) public class StrategyA implements Strategy{ @Override public void doStrategy() { } } //上下文操作类, 屏蔽高层模块对策略的直接访问 public class Context { private Strategy strategy = null; public Context(Strategy strategy) { this.strategy = strategy; } public void doStrategy() { strategy.doStrategy(); } }
实际代码
/** * 渠道活动创建策略接口 * */ public interface ActivityStrategy { /** * 创建渠道活动配置 * * @param param 活动参数 * @return */ void createActivity(ActParam param); } /** * 活动模板类 * */ @Slf4j public abstract class AbstractActivityTemplate implements ActivityStrategy { /** * 抽象方法:具体渠道活动创建 * */ protected abstract ProcessResult createChannelActivity(ActParam param); @Override public ProcessResult createActivity(ActParam param) { //保存活动基础信息 saveActBaseConfig(param); //保存具体渠道配置 createChannelActivity(param); //审计日志上报 ... //消息通知 ... } } /** * 短信推送策略具体实现类 * */ @Component public class SmsChannelActivityStrategy extends AbstractActivityTemplate { @Override public void createChannelActivity(ActParam param) { //保存短信配置数据 } } (其他渠道活动类似,此处省略) /** * 策略调用入口 * */ @Slf4j @Component public class ActivityContext { @Resource private ActivityStrategyFactory activityStrategyFactory ; public void create(ActParam param) { //通过前面的工厂模式的代码,获取具体渠道对应的策略类 ActivityStrategy strategy = activityStrategyFactory.getActivityStrategy(param.ChannelType); //执行策略 strategy.createActivity(param); } }
实际编码过程中,我们加入了ChannelActivityStrategy作为渠道活动创建策略接口,并用模板类AbstractActivityTemplate实现该接口,同时结合工厂模式创建具体策略,至此将三种模式结合了起来。
实践总结
策略模式在项目开发过程中经常用于消除复杂的if else复杂逻辑,后续如果有新的渠道活动时,只需要新增对应渠道的活动创建逻辑即可,可以十分便捷地对系统业务进行扩展。
在项目实践过程,经常会将工厂模式、模板方法模式和策略模式一起结合使用。模板方法模式进行业务流程公共骨架的抽取,策略模式进行具体子流程策略的实现和调用的封装,而工厂模式可以进行子流程策略的创建。
多种模式的结合使用可以充分发挥出各个模式的优势,达到真正提升系统设计扩展性的目的。