设为首页 加入收藏

TOP

按钮条件逻辑配置化的可选技术方案(一)
2018-09-05 11:46:46 】 浏览:845
Tags:按钮 条件 逻辑 配置 技术 方案

问题

详情页的一些按钮逻辑,很容易因为产品的策略变更而变化,或因为来了新业务而新增条件判断,或因为不同业务的差异性而有所不同。如果通过代码来实现,通常要写一串if-elseif-elseif-else语句,且后续修改扩展比较容易出错,需要重新发布,灵活性差。 可采用配置化的方法来实现按钮逻辑,从而在需要修改的时候只要变更配置即可。按钮逻辑的代码形式一般是:

public Boolean getIsAllowBuyAgain() {
  if (ConditionA) {
    return BoolA;
  }
  if (ConditionB) {
    return BoolB;
  }
 
  if (CondtionC && !CondtionD && (ConditionE not in [v1,v2])) {
    return BoolC;
  }
  return BoolD;
}

本文讨论了三种可选方案: 重量级的Groovy脚本方案、轻量级的规则引擎方案、超轻量级的条件匹配表达式方案,重点讲解了条件匹配表达式方案。

这里的代码实现仅作为demo, 实际需要考虑健壮性及更多因素。 按钮逻辑实现采用了“组合模式”,解析配置采用了“策略模式”和“工厂模式”。

使用Groovy缓存脚本

优点:非常灵活通用,重量级配置方案

不足:耗时可能比较多,简单script脚本第一次执行比较慢, script脚本缓存后执行比较快, 可以考虑预热; 复杂的代码不易于配置,简单逻辑是可以使用Groovy配置的。

package button

import com.alibaba.fastjson.JSON
import org.junit.Test
import shared.conf.GlobalConfig
import shared.script.ScriptExecutor
import spock.lang.Specification
import spock.lang.Unroll
import zzz.study.patterns.composite.button.*

class ButtonConfigTest extends Specification {

    ScriptExecutor scriptExecutor = new ScriptExecutor()
    GlobalConfig config = new GlobalConfig()

    def setup() {
        scriptExecutor.globalConfig = config
        scriptExecutor.init()
    }

    @Test
    def "testComplexConfigByGroovy"() {
        when:
        Domain domain = new Domain()
        domain.state = 20
        domain.orderNo = 'E0001'
        domain.orderType = 0

        then:
        testCond(domain)
    }

    void testCond(domain) {
        Binding binding = new Binding()
        binding.setVariable("domain", domain)
        def someButtonLogicFromApollo = 'domain.orderType == 10 && domain.state != null && domain.state != 20'
        println "domain = " + JSON.toJSONString(domain)

        (0..100).each {
            long start = System.currentTimeMillis()
            println "someButtonLogicFromApollo ? " +
                    scriptExecutor.exec(someButtonLogicFromApollo, binding)
            long end = System.currentTimeMillis()
            println "costs: " + (end - start) + " ms"
        }

    }
}

class Domain {

    /** 订单编号 */
    String orderNo

    /** 订单状态 */
    Integer state

    /** 订单类型 */
    Integer orderType

}
package shared.script;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import groovy.lang.Binding;
import groovy.lang.Script;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import shared.conf.GlobalConfig;

@Component("scriptExecutor")
public class ScriptExecutor {

  private static Logger logger = LoggerFactory.getLogger(ScriptExecutor.class);

  private LoadingCache<String, GenericObjectPool<Script>> scriptCache;

  @Resource
  private GlobalConfig globalConfig;

  @PostConstruct
  public void init() {
    scriptCache = CacheBuilder
        .newBuilder().build(new CacheLoader<String, GenericObjectPool<Script>>() {
          @Override
          public GenericObjectPool<Script> load(String script) {
            GenericObjectPoolConfig poolConfig = new G
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Map大家族的那点事儿(3) :TreeMap 下一篇SpringBoot | 第十六章:web 应用..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目