快速迭代业务背景下:设计模式混编的一次尝试

​一、业务背景

在现有集团业务中,快速迭代已经是家常便饭了,立项需求,评估排期,进入开发,提前进入测试。作为项目任何一个环节的小伙伴应该都有体会,快速反应快速迭代已经成为常态。如果假如你参与的是一个稳定服务的中台项目,那么还好一些能够持续的思考优化项目。如果只是一种活动性质的项目,这次做完下次业务就完全不一样了加上时间短任务重,导致没有那么多时间去做设计,设计模式的作用在这种类型的项目中作用就被削弱了。设计模式的使用比较常见是中间件、技术框架中。因为设计模式的主要能力是解耦,所以在中间件和技术框架中便发挥了很大的优势。在接入集团安全风控的接口工作中,做了一些思考,接入安全风控有两种方式,一种是通过http网关方式,另外一种是通过rpc网关的方式,rpc方式更加灵活,http更加简洁,配置成本更低。由于业务发展,现在需要根据不同的渠道做不同的预算,因此需要配置不同的安全策略,每一种策略对应一种安全码作为接入标识,同时考虑到后期针对不同的业务会定制自己的安全策略。因此做了一个设计模式混编的设计,目的是为了“一劳永逸”。如果对这种“一劳永逸”感兴趣,请继续阅读下去,相信你会有收获。

 

二、设计思路

结合业务背景,考虑到将来会拓展更多的渠道,每次接入都需要进行扩展,如果在接入中使用if else会影响以前的代码,测试需要回归,只会增加测试工作量。考虑到以上原因,现在使用门面模式+责任链+策略模式,本着OCP原则,对修改关闭,对扩展开放。

思路类图:

三、模式优缺点总结

1、门面模式

门面模式定义:门面模式(Facade Pattern) 也叫做外观模式,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口, 使得子系统更易于使用。 

类图如下:

1.1门面调用示例代码

public class AlisecurityFacade implements IalisecurityFacade {
    @Autowired
    private StrategeContext context;

    /**
     * 门面属于高层模块,一旦设计好和外部系统的交互规则,一般不再做改动,
     * 相应的业务变动在底层模块中,此处底层模块是责任链和策略
     * @param channelCode
     * @param token
     * @return
     */
    @Override
    public  Boolean verify(int channelCode,String token) {
        CheckUtil.isEmpty(channelCode, "渠道号");
        CheckUtil.isEmpty(token, "token");
        //通过门面访问具体的操作,遵守【迪米特法则】
        Boolean result = context.fire(channelCode,token);
        return result;
    }

}

 

1.2门面封装示例

通过统一的入口调用,只需要提供一个方法入口,调用者无需关注方法中的具体逻辑流程,就算方法里“败絮其中”,方法外也可以“金玉其外”,遵循迪米特法则,最少知道原则。

public class StrategeContext {
    @Autowired
    private StrategyMofun strategyMofun;

    @Autowired
    private StrategyTmall strategyTmal;

    /**
     * 通过封装,对链路比较长的逻辑进行封装,供门面使用,每一条逻辑尽量遵循【单一职责原则】
     * @param channelCode
     * @param token
     * @return
     */
    public Boolean fire(int channelCode,String token) {
        //①流程一:设置责任链顺序
        strategyTmal.setNextHandler(strategyMofun);
        //②流程二:通过责任链选择合适的策略
        Istratege excuteStratege = strategyTmal.handleMessage(channelCode);
        //③流程三:调用策略中的安全校验方法
        Boolean result = excuteStratege.verifySecurity(token);
        return result;
    }

}

 

1.3门面优点

1.3.1减少系统的相互依赖


想想看, 如果我们不使用门面模式, 外界访问直接深入到子系统内部, 相互之间是一种强耦合关系, 你死我就死, 你活我才能活, 这样的强依赖是系统设计所不能接受的, 门面模式的出现就很好地解决了该问题, 所有的依赖都是对门面对象的依赖, 与子系统无关。

1.3.2提高了灵活性


依赖减少了, 灵活性自然提高了。不管子系统内部如何变化, 只要不影响到门面对象,任你自由活动。

 

1.3.3 提高安全性


想让你访问子系统的哪些业务就开通哪些逻辑, 不在门面上开通的方法, 你休想访问到

 

1.4门面缺点

 

门面是最顶层的逻辑,如果门面实在无法满足交互需求,门面改动,底层会有蝴蝶效应,改动会很大,门面设计要慎重。

1.5

门面模式的使用场景

1.5.1

为一个复杂的模块或子系统提供一个供外界访问的接口

1.5.2

子系统相对独立——外界对子系统的访问只要黑箱操作即可

 

2、责任链模式

定义:使多个对象都有机会处理请求, 从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链, 并沿着这条链传递该请求, 直到有对象处理它为止 , 责任链模式的重点是在“链”上, 由一条链去处理相似的请求在链中决定谁来处理这个请求, 并返回相应的结果 。
 

类图如下:

2.1示例代码

@Slf4j
@Component
public class StrategyMofun extends Handler implements Istratege {

    @Autowired
    private  StrategyMofun strategyMofun;

    /**
     * 安全码
     */
    @Value("${alibaba.security.mofun.asac}")
    private String asac;

    @Autowired
    private RmbService rmbService;

    public StrategyMofun() {
        super(Handler.MOFUN_CHANNEL_CODE);
    }

    @Override
    public Boolean verifySecurity(String token) {
        log.info("小程序策略校验逻辑");
        // 在此处写具体的安全校验逻辑策略
        if (!SwitchProperties.mofunRmbStatus.get()) {
            return true;
        }
        return rmbService.getRmbStatus(token, asac);
    }

    @Override
    public Istratege getStrategeInstance() {
//        Class<? extends StrategyMofun> aClass = this.getClass();
//        String className = aClass.getName();
        return strategyMofun;
    }
}
@Slf4j
public abstract class Handler {
    //天猫、淘宝的渠道code
    public final static int TMALL_CHANNEL_CODE = 2;
    //魔放的渠道code
    public final static int MOFUN_CHANNEL_CODE = 1;
    private int channelCode = 0;

    /**
     * 责任传递,下一个责任处理者是谁
     */
    private Handler nextHandler;
    /**
     * 获得责任链上责任者的实例(策略)
     */
    private Object stratege;

    public Handler(int channelCode) {
        this.channelCode = channelCode;
    }

    public Istratege handleMessage(int channelCode) {
        Istratege strategeInstance = getStrategeInstance();
        if (channelCode == this.channelCode) {
//            Istratege stratege = null;
//            try {
//                //拿到责任链中节点的实例
//                stratege = (Istratege) Class.forName(strategeInstance).newInstance();
//            } catch (Exception e) {
//                log.error("使用反射生成策略实例异常{}", e);
//            }
            return strategeInstance;
        } else {
            //有后续责任者,把请求接着往下传
            if (this.nextHandler != null) {
                return this.nextHandler.handleMessage(channelCode);
            } else {
                //没有后继处理的责任人,应放过,不做拦击校验
                return null;
            }
        }
    }

    /**
     * 如果不属于当前责任者,应传递下一个责任者
     *
     * @param handler
     */
    public void setNextHandler(Handler handler) {
        this.nextHandler = handler;
    }

    protected abstract Istratege getStrategeInstance();
}

 

2.1优点

责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的, 处理者可以不用知道请求的全貌(例如在J2EE项目开发中, 可以剥离出无状态Bean由责任链处理) , 两者解耦, 提高系统的灵活性

 

2.2缺点

责任链有两个非常显著的缺点:一是性能问题, 每个请求都是从链头遍历到链尾, 特别是在链比较长的时候, 性能是一个非常大的问题。二是调试不很方便, 特别是链条比较长,环节比较多的时候, 由于采用了类似递归的方式, 调试的时候逻辑可能比较复杂

 

2.3 责任链模式的注意事项

链中节点数量需要控制, 避免出现超长链的情况, 一般的做法是在Handler中设置一个最大节点数量, 在setNext方法中判断是否已经是超过其阈值, 超过则不允许该链建立, 避免无意识地破坏系统性能。

 

3、策略模式

定义:定义一组算法, 将每个算法都封装起来, 并且使它们之间可以互换

类图如下:

3.1示例代码

public interface Istratege {
    /**
     * 策略的抽象接口
     * @param token
     * @return
     */
     Boolean verifySecurity(String token);
}

@Slf4j
@Component
public class StrategyMofun extends Handler implements Istratege {

    @Autowired
    private  StrategyMofun strategyMofun;

    /**
     * 安全码
     */
    @Value("${alibaba.security.mofun.asac}")
    private String asac;

    @Autowired
    private RmbService rmbService;

    public StrategyMofun() {
        super(Handler.MOFUN_CHANNEL_CODE);
    }

    @Override
    public Boolean verifySecurity(String token) {
        log.info("小程序策略校验逻辑");
        // 在此处写具体的安全校验逻辑策略
        if (!SwitchProperties.mofunRmbStatus.get()) {
            return true;
        }
        return rmbService.getRmbStatus(token, asac);
    }

    @Override
    public Istratege getStrategeInstance() {
//        Class<? extends StrategyMofun> aClass = this.getClass();
//        String className = aClass.getName();
        return strategyMofun;
    }
}

3.2优点

3.2.1算法可以自由切换


这是策略模式本身定义的, 只要实现抽象策略, 它就成为策略家族的一个成员, 通过封装角色对其进行封装, 保证对外提供“可自由切换”的策略。

 

3.2.2 避免使用多重条件判断


如果没有策略模式, 我们想想看会是什么样子?一个策略家族有5个策略算法, 一会要使用A策略, 一会要使用B策略, 怎么设计呢?使用多重的条件语句?多重条件语句不易维护, 而且出错的概率大大增强。使用策略模式后, 可以由其他模块决定采用何种策略, 策略家族对外提供的访问接口就是封装类, 简化了操作, 同时避免了条件语句判断。

 

3.2.3扩展性良好


这甚至都不用说是它的优点, 因为它太明显了。在现有的系统中增加一个策略太容易了, 只要实现接口就可以了, 其他都不用修改, 类似于一个可反复拆卸的插件, 这大大地符合了OCP原则

 

3.3缺点

策略类数量增多
每一个策略都是一个类, 复用的可能性很小, 类数量增多。

 

3.4 策略模式的使用场景

3.4.1多个类只有在算法或行为上稍有不同的场景。

3.4.2算法需要自由切换的场景。


例如, 算法的选择是由使用者决定的, 或者算法始终在进化, 特别是一些站在技术前沿的行业, 连业务专家都无法给你保证这样的系统规则能够存在多长时间, 在这种情况下策略模式是你最好的助手。

 

3.4.3需要屏蔽算法规则的场景。


现在的科技发展得很快, 人脑的记忆是有限的(就目前来说是有限的) , 太多的算法你只要知道一个名字就可以了, 传递相关的数字进来, 反馈一个运算结果, 万事大吉。

 

3.5 策略模式的注意事项

 

如果系统中的一个策略家族的具体策略数量超过4个, 则需要考虑使用混合模式, 解决策略类膨胀和对外暴露的问题。

四、调试笔记

使用中,在选择链中节点实例返回时,使用了反射生成实例,生成的实例会把依赖的注入实例过滤掉,因为是组合关系,反射目标类时,组合类不作为目标类的元数据来进行加载。这个地方也犯了一个很大的思考错误,项目本来已经使用spring依赖注入了,自己还多此一举,就是在骑驴找驴。

反射的性能问题:

导致反射慢的两个原因:

①,反射是基于程序集和元数据的,在使用反射的时候,会搜索元数据,而元数据是基于字符串的,并且无法预编译,所以这一系列的操作对性能有影响。

②,大量的装箱拆箱也对性能有影响。由于我们对目标类型是未知的,而且向方法传递的参数通常是object类型的,所以会有大量的装箱和拆箱。

反射使用注意:

反射大概比直接调用慢50~100倍,但是需要在执行100万遍的时候才会有所感觉判断一个函数的性能,需要把这个函数执行100万遍甚至1000万遍如果只是偶尔调用一下反射,可以不必关心反射带来的性能影响如果需要大量调用反射,需要考虑缓存。编程的思想才是限制程序性能的最主要的因素

五、总结

看完以后,是否感觉可以“一劳永逸”,只要是在运行的业务就不可能会真正的一劳永逸,但是设计模式可以让程序足够松耦合甚至可插拔,下次迭代开发时能够达到减少复杂度,相对一劳永逸的效果。

交流

马老师曾经说过,“未来数据是共同分享的,才能提现它的价值!“,如果有更多项目使用场景,欢迎留言交流


如果您已阅读到此,感谢您对公众号里文章的认可,请动动你可爱的小指头关注微信公众号,每天都有硬核技术文章推送,就算离开也能找到回家的路

二维码如下或者公众号搜索“程序员的十万个为什么”:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的十万个为什么

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值