关于java:设计模式如何提升-vivo-营销自动化业务扩展性-引擎篇01

71次阅读

共计 12481 个字符,预计需要花费 32 分钟才能阅读完成。

在《vivo 营销自动化技术解密 | 开篇》中,咱们从整体上介绍了 vivo 营销自动化平台的业务架构、外围业务模块性能、零碎架构和几大核心技术设计。

本次带来的是系列文章的第 2 篇,本文具体解析设计模式和相干利用如何帮忙营销自动化业务晋升零碎扩展性,以及实际过程中的思考和总结。

一、引言

营销业务自身极具简单多变性,特地是随同着数字化营销蓬勃发展的趋势,在市场的不同期间、公司倒退的不同阶段、面向不同的用户群体以及继续成果稳定迭代,都会产生不同的营销策略决策。

当面对随时变动的业务场景时,零碎的扩展性就显得十分重要。而在谈到零碎设计扩展性的时候,总是首先会想到设计准则和设计模式。但设计模式不是银弹,并不能解决所有问题,它只是前人提炼总结进去的招式办法,须要开发者依据理论业务场景进行正当的抉择、适合的变通,能力真正去解决理论场景中的问题,并总结造成本人的方法论。

那么接下来咱们看看设计模式是如何帮忙咱们在营销策略引擎中晋升零碎扩展性的。

二、营销策略引擎

先简略介绍一下营销策略引擎:策略引擎是通过搭建可视化流程组件,定义各个流程节点,自动化执行流动业务流程,从而提供不同经营流动能力。其中外围流动业务流程次要包含三大部分:经营流动配置 -> 经营流动审批 -> 经营流动执行

  • 经营流动配置:经营人员在零碎后盾配置经营流动。包含流动名称、流动工夫、触发条件、流动用户和具体推送渠道(如短信、微信、push 推送等)。
  • 经营流动审批:品质 / 主管人员审批经营流动配置。审批流程波及了流动审批节点和人员的配置,审批相干的回调操作配置。
  • 经营流动执行:零碎自动化执行经营流动的过程。即具体的渠道如短信、微信、push 等推送流动的工作执行下发流程,包含用户数据筹备,数据下发推送和数据成果回收等。

三、设计模式具体利用

3.1 经营流动配置

3.1.1 工厂模式

具体场景

个别状况下,依据不同的用户和流动场景,经营借助数据分析会决策出不同的流动策略,比方须要创立短信推送策略、微信图文推送策略、App Push 推送策略等。此时咱们能够应用工厂模式,对立治理具体推送策略的创立。

模式分析

在 GoF《设计模式:可复用面向对象软件的根底》中:工厂模式被分成了工厂办法和形象工厂两类,而简略工厂模式(又称动态工厂模式)被看作是工厂办法的一种特例。不过因为简略工厂和工厂办法绝对更简略和易于了解,代码可读性也更强,因而在理论我的项目中更加罕用。

其中简略工厂的实用场景:

  • a. 工厂类负责创立的对象比拟少,工厂办法中的创立逻辑简略。
  • b. 客户端毋庸关怀创立具体对象的细节,仅需晓得传入工厂类的类型参数。

而工厂办法的实用场景:

  • a. 工厂类对象创立逻辑绝对简单,须要将工厂实例化提早到其具体工厂子类中。
  • b. 适宜需要变更频繁的场景,能够利用不同的工厂实现类反对新的工厂创立计划,更合乎开闭准则,扩展性更好。

典型代码示例

// 形象产品类
public abstract class Product {public abstract void method();
}
// 具体的产品类 
class ProductA extends Product {
    @Override
    public void method() {// 具体的执行逻辑}
}
// 形象工厂模板类
abstract class Factory<T> {abstract Product createProduct(Class<T> c);
}
// 具体工厂实现类
class FactoryA extends Factory{
    @Override
    Product createProduct(Class c) {Product product = (Product) Class.forName(c.getName()).newInstance();
        return product;
    }
}

理论代码

/**
 * @author chenwangrong
 * 流动策略工厂类
 */
@Component
@Slf4j
public class ActivityStrategyFactory {
 
    /**
     * 取得渠道类型对应的策略
     *
     * @param channelType channelType
     * @return OperationServiceStrategy
     */
    public static ActivityStrategy getActivityStrategy(ChannelTypeEnum channelType) {ChannelTypeStrategyEnum channelTypeStrategyEnum = ChannelTypeStrategyEnum.getByChannelType(channelType);
        Assert.notNull(channelTypeStrategyEnum , "指定的渠道类型 [channelType=" + channelType + "] 不存在");
 
        String strategyName= channelTypeStrategyEnum.getHandlerName();
        Assert.notNull(strategyName, "指定的渠道类型[channelType=" + channelType + "未配置策略");
 
        return (ActivityStrategy)SpringContextHolder.getBean(handlerName);
    }
 
 
    public enum ChannelTypeStrategyEnum {
        /**
         * 短信渠道
         */
        SMS(ChannelTypeEnum.SMS, "smsActivityStrategy"),
        /**
         * 微信渠道
         */
        WX_NEWS(ChannelTypeEnum.WX, "wxActivityStrategy"),
        /**
         * push 渠道
         */
        PUSH(ChannelTypeEnum.PUSH, "pushActivityStrategy"),;
 
        private final ChannelTypeEnum channelTypeEnum;
 
        private final String strategyName;
 
        ChannelTypeStrategyEnum (ChannelTypeEnum channelTypeEnum, String strategyName) {
            this.channelTypeEnum = channelTypeEnum;
            this.strategyName= strategyName;
        }
 
 
        public String getStrategyName() {return strategyName;}
 
        public static ChannelTypeStrategyEnum getByChannelType(ChannelTypeEnum channelTypeEnum) {for (ChannelTypeStrategyEnum channelTypeStrategyEnum : values()) {if (channelTypeEnum == channelTypeStrategyEnum.channelTypeEnum) {return channelTypeStrategyEnum ;}
            }
            return null;
        }
    }
}

实际总结

在理论我的项目代码中咱们采纳的是 简略工厂模式(动态工厂模式),实现时利用枚举(或者映射配置表)来保留渠道类型与具体策略实现类的映射关系,再联合 Spring 的单例模式,来进行策略类的创立。

相比于工厂办法模式,在满足业务的前提下,缩小了工厂类数量,代码更加简略实用。

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 简单逻辑,后续如果有新的渠道流动时,只须要新增对应渠道的流动创立逻辑即可,能够非常便捷地对系统业务进行扩大。

在我的项目实际过程,常常会将工厂模式、模板办法模式和策略模式一起联合应用。模板办法模式进行业务流程公共骨架的抽取,策略模式进行具体子流程策略的实现和调用的封装,而工厂模式能够进行子流程策略的创立。

多种模式的联合应用能够充分发挥出各个模式的劣势,达到真正晋升零碎设计扩展性的目标。

3.2 经营流动执行

3.2.1 状态模式

具体场景

在经营流动的执行过程中,会波及活动状态的变更,以及变更前的条件检测和变更后的操作解决。与之绝对应地,咱们很容易就会想到状态模式。

模式分析

在 GoF 经典的《设计模式:可复用面向对象软件的根底》中:状态模式容许一个对象在其外部状态扭转的时候扭转其行为。

状态模式的作用就是拆散状态的行为,通过保护状态的变动,来调用不同状态对应的不同性能。它们的关系能够形容为:状态决定行为。因为状态是在运行期被扭转的,因而行为也会在运行期随着状态的扭转而扭转。

典型代码示例

/**
 * 状态模式
 * 形象状态类
 * */
interface State {
    // 状态对应的解决
    void handle()}
 
  
// 具体状态关现类
public  class ConcreteStateA implements  State {
    @Override
    public void handle() {}
}
 
public  class ConcreteStateB implements  State {
    @Override
    public void handle() {}
}
 
// 环境类 Context,拜访入口
public class Context {
    // 持有一个 State 类型的对象实例
    private State state;
 
    public void setState(State state) {this.state = state;}
      
    public void request() {
        // 转调 state 来解决
        state.handle();}
}
 
public class Client {public static void main(String[] args){
        // 创立状态
        State state = new ConcreteStateB();
        // 创立环境
        Context context = new Context();
        // 将状态设置到环境中
        context.setState(state);
        // 申请
        context.request();}
}

实际总结

在理论软件我的项目开发中,业务状态不多且状态转移简略的场景,可应用状态模式来实现;但如果是波及的业务流程状态转移繁冗时,应用状态模式会引入十分多的状态类和办法,当状态逻辑有变更时,代码也会变得难以保护,此时应用状态模式并不非常适宜。

而当流程状态繁多,事件校验和触发执行动作蕴含的业务逻辑比较复杂时,如何去实现呢?

这里咱们必须停下来思考:应用设计模式只是解决理论问题的一种伎俩,但设计模式不是一把“万能的”锤子,须要分明地理解到它的劣势和有余。而这种问题场景下,业界曾经有一个更通用的计划——无限状态机,通过更高层的封装,提供给业务更便捷的利用。

3.2.2 状态模式的利用——无限状态机

无限状态机(Finite-State Machine , 缩写:FSM),业界简称状态机。它亦是由 事件 状态 动作 三大部分组成,三者的关系是:事件触发状态的转移,状态的转移触发后续动作的执行。状态机能够基于传统的状态模式硬编码来实现,也能够通过 数据库 / 文件配置 或者 DSL 的形式 来保留状态及转移配置来实现(举荐)。

业界中也已涌现出了不少开源状态机的框架,比拟罕用的有 Spring-statemachine(Spring 官网提供)、squirrel statemachine 和阿里开源的 cola-statemachine。

理论利用

在理论我的项目开发中,咱们针对本身业务的特点:业务流程状态多,然而事件触发和状态变更动作绝对简略,故而抉择了无状态、更加轻量级的解决方案——基于开源的状态机实现思维进行开发。(对于状态机的实现和应用选型会在后续的文章中做进一步的剖析,感兴趣的童鞋能够拜访官网先做理解)。

实际代码

/**
 * 状态机工厂类
 */
public class StatusMachineEngine {private StatusMachineEngine() { }
    private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap();
 
    static {
        // 短信推送状态
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine");
        //PUSH 推送状态
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine");
        //......
    }
 
    public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) {return STATUS_MACHINE_MAP.get(channelTypeEnum);
    }
 
   /**
     * 触发状态转移
     * @param channelTypeEnum
     * @param status 以后状态
     * @param eventType 触发事件
     * @param context 上下文参数
     */
    public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) {StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum));
        // 推动状态机进行流转,具体介绍本期先省略
        orderStateMachine.fireEvent(status, eventType, context);
    }
 
/**
 * 短信推送流动状态机初始化
 */
@Component
public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> {
 
    @Autowired
    private  StatusAction smsStatusAction;
    @Autowired
    private  StatusCondition smsStatusCondition;
 
    // 基于 DSL 构建状态配置,触发事件转移和后续的动作
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {StateMachineBuilder<String, EventType, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(INIT)
                .to(NOT_START)
                .on(EventType.TIME_BEGIN)
                .when(smsStatusAction.checkNotifyCondition())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(NOT_START)
                .to(DATA_PREPARING)
                .on(EventType.CAL_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(DATA_PREPARING)
                .to(DATA_PREPARED)
                .on(EventType.PREPARED_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        ...(省略其余状态)
        builder.build(StatusMachineEngine.getMachineEngine(ChannelTypeEnum.SMS));
    }
 
   // 调用端
   public class Client {public static void main(String[] args){
          // 构建流动上下文
          Context context = new Context(...);
         // 触发状态流转
          StatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context);
      }
   }
}

通过预约义状态转换流程的形式,实现 ApplicationListener 接口,在利用启动时将事件、状态转移条件和触发操作的流程加载到状态机工作内存中,由事件触发驱动状态机进行主动流转。

实际总结

理论场景中,不用强行套用设计模式,而是该当充沛联合业务的特点,同时针对设计模式的优劣势,进行更加适合的选型或者进一步扩大。

3.3 自动化经营流动审批

3.3.1 设计模式的综合利用——工作流引擎

具体场景

为了做好品质和危险管控,流动创立须要退出审批环节,把控经营流动的公布执行,同时对于不同类型的经营流动,可能波及的业务畛域和部门各不相同,审批管控人员也不一样,须要配置绝对应的审批关系。

此时须要做到:

  • a. 审批流程全配置化,易批改和增加;
  • b. 业务流程节点可自在编排,组件专用化;
  • c. 流程数据长久化,审批过程数据须要进行操作监控。

针对这方面的需要,业界有一套通用的业务工具——工作流引擎。工作流引擎显然并不属于具体某一种设计模式的实现,它是涵盖了多种设计模式的组件利用。

不仅仅是审批性能,其实后面 自动化营销流程引擎设计 也同样是应用工作流引擎 搭建流程组件

状态机 VS 工作流引擎

工作流引擎和状态机仿佛存在十分多的相似之处,都能够通过定义流程的节点、转移条件和相应触发的操作来实现业务流程。如果只从实用场景的复杂性上看,状态机更实用于单维度的业务问题,可能清晰地描绘出所有可能的状态以及导致转换的事件,更加灵便轻便;而工作流引擎则更适宜业务流程治理,解决如大型 CRM 复杂度更高的流程自动化问题,能够改善整体业务流程的效率。

在业界的工作流引擎中,比拟驰名的有 Activiti 和 JBPM 等。(对于状态机和工作流引擎的比照、开源工作流引擎的具体介绍和选型,以及如何自行开发构建一款根本的工作流引擎组件,同样是会在后续的文章中做进一步剖析,本文因为主题和篇幅的起因暂不做具体介绍。)

在理论开发过程中,咱们是基于开源的 Activiti 工作流引擎自研了一套简易版的工作流引擎,精简了许多相干的配置,只留下了外围流程操作和数据记录。

工作流引擎流程图:

实际总结

工作流引擎是涵盖了多种设计模式的利用组件,只有在复杂多变的业务场景中才须要利用,须要联合业务进行认真评估。在 适合的场景应用适合的解决方案,遵循零碎架构设计的简略、适合、可演变准则,不适度设计。

四、总结

本文基于自动化营销的业务实际,剖析介绍了工厂办法模式、模板办法模式、策略模式以及状态模式这四种模式在我的项目开发中的具体实现过程。也在单纯的模式之外介绍了状态机和工作流引擎这些涵盖了多种设计模式零碎组件,并分享了过程中的抉择和思考。

面对业务复杂多变的需要,须要时刻关注零碎设计的复用性和可扩展性,而设计准则和设计模式能够在零碎设计实现时给予咱们方向性的领导,同时更须要依据理论业务场景进行正当的抉择,适合的变通,不断完善本人的方法论。

后续咱们将带来系列专题文章的其余内容,每一篇文章都会对外面的技术实际进行详尽解析,敬请期待。

作者:vivo 互联网服务器团队 -Chen Wangrong

正文完
 0