zoukankan      html  css  js  c++  java
  • 将通用性的技术逻辑与差异性的业务逻辑相分离

    背景###

    很多业务代码,将通用性的技术逻辑与差异性的业务逻辑混杂在一起,这样的做法导致:

    • 业务意图不容易很快识别出来,或者要费力思考业务语义;
    • 当要复用相同的处理逻辑时,则不得不复制一份。

    开发人员常常要花更多的力气去理解业务代码,隐形之中增加了很多理解与维护成本。遗憾的是,很多开发人员还并没有充分意识到这一点,甚至觉得这无伤大雅。

    实际上,这个细微的问题,反映的却是开发人员普遍缺乏设计性的思考。 如果一个开发人员重视设计思维,他就会发现,这不仅仅是编程细节,而是可以运用设计去掌控的事情。

    那么,如何将通用性的技术逻辑与差异性的业务逻辑相分离呢? 首先,要明白什么是技术逻辑,什么是业务逻辑。

    比如:遍历一个商品对象列表,取出所有商品的标题列表。

    • 技术逻辑:通常是一些通用处理,比如遍历一个对象列表,取出对象中的某个属性。 我并不关心是什么对象列表,或对象的什么属性。 我只关心遍历列表及如何取出对象中的某个属性。 技术逻辑通常是需要对客户端屏蔽的底层细节。

    • 业务逻辑: 通常是差异性的处理,比如商品列表与商品标题(可能是JSON中的某个字段)。 我并不关心如何遍历列表或取出对象属性的技术细节,我只关心商品及商品标题。业务逻辑通常是领域需要重点关注的领域知识,要清晰地凸显出来。

    下面,将以一个示例来说明,如何将将通用性的技术逻辑与差异性的业务逻辑相分离。

    示例###

    请看下面这段从主流程里抽出来的代码片段。代码清单一:

    List<Integer> packIds = orderDeliveryResult.getData().stream()
                    .filter(x->x.getDeliveryState() == 1 || x.getDeliveryState() == 2 )
                    .map(x->x.getId()).collect(Collectors.toList());
    

    你能一眼看出这段代码的含义吗? 哦,看上去是要拿到一个包裹ID 列表,可是 x.getDeliveryState() == 1 || x.getDeliveryState() == 2 是什么意思呢? 你得去找作者沟通一下了。

    这段代码是将技术逻辑和业务逻辑混杂在一起的典型例子。对于业务语义来说,实际上并不关心 stream, filter, map 这种技术细节。下面看看改写后会是什么样:

    
        List<Integer> packIds = getDeliveredPackIds(orderDeliveryResult.getData());
    
        private List<Integer> getDeliveredPackIds(List<OrderDelivery> orderDelivery) {
            return StreamUtil.filterAndMap(orderExpresses, oe -> isDelivered(oe), OrderDelivery::getId);
        }
    
        private boolean isDelivered(OrderDelivery oe) {
            return oe.getDeliveryState() == 1 || oe.getDeliveryState() == 2;
        }
    
    public class StreamUtil {
    
      private StreamUtil() {}
    
      public static <T,R> List<R> map(List<T> dataList, Function<T,R> getData) {
        if (CollectionUtils.isEmpty(dataList)) { return new ArrayList(); }
        return dataList.stream().map(getData).collect(Collectors.toList());
      }
    
      public static<T,R> List<R> filterAndMap(List<T> dataList, Predicate<? super T> predicate , Function<T,R> getData) {
        if (CollectionUtils.isEmpty(dataList)) { return new ArrayList(); }
        return dataList.stream().filter(predicate).map(getData).collect(Collectors.toList());
      }
    }
    

    改写后:

    • 原来主流程里的代码变成了一行: getDeliveredPackIds(orderDeliveryList); 业务语义凸显出来了: 哦,原来是要拿到已发货的包裹列表。一目了然,在理解主流程时也不需要切换到理解这种细节层面的东西了。

    • 原来的 stream, filter, map 被分离到 StreamUtil 工具类中,后面可以反复使用,并且在实现业务逻辑时,再也不需要关心如何遍历列表、过滤条件、拿到返回值列表这种技术细节了。

    • 分离出了领域知识:isDelivered。 这个实际上应该写到 orderDelivery 类中。这样 oe -> isDelivered(oe) 写成更简洁的形式: OrderDelivery::isDelivered

    新的实现方式,实现了一箭三雕:

    • 凸显业务语义 ;
    • 代码复用;
    • 沉淀领域知识。

    这就是将通用技术逻辑与业务逻辑分离的三大重要益处!


    反思###

    语言特性

    Java8 提供了 stream ,于是开发人员在应用系统里到处留下了 stream 的足迹; 可是你理解其初衷吗?

    stream 的目的是能够以一种声明式的方式清晰地凸显意图,然而,开发人员很快就将意图扔到一边,只图写法的快感,根本没有体现出这种语言特性的初衷。 也许 Java8 的提供者,应该多提供一个 StreamUtil 的类,才会让开发人员意识到其根本目的所在。

    应当深思语言特性提供的根本目的,并加以适当包装,以便让客户端用更清晰、易懂、便利的方式去表达现实流程和规则,而不是贪图品尝一下新鲜特性。


    设计思维

    为什么说体现了设计思维呢?

    可扩展性设计,本质上是将变化与不变分离。 技术逻辑代表了通用不变的部分,而业务逻辑代表了复杂多变的部分。将通用性的技术逻辑与差异性的业务逻辑相分离,本质上体现了可扩展性设计思维。虽然微小的编程问题并不足以体现其优势,但确确实实能很好地锻炼可扩展性设计思维。这种思维可以通过每一次的“将通用性的技术逻辑与差异性的业务逻辑相分离”的思考和实践进行强化。

    远方,即在足下。


    领域思维

    领域思维,实际上是一个附加效果。当把通用性的技术逻辑提炼出来后,就能更清晰地看到所要表达的内容,即:领域知识和领域规则。这能让开发人员更容易意识到真正的关注点所在,而不是沉溺于技术和编程细节。

    同时,当清晰地看到领域知识和领域规则的时候,就会思考将它放置在合适的位置,比如实体能力或领域服务, 而不是淹没在流程代码里。


    小结###

    将通用性的技术逻辑与差异性的业务逻辑分离,实现了一箭三雕:凸显业务语义 ;代码复用;沉淀领域知识。 编程表达的小小差异,不仅仅体现出设计思维,还体现了一个开发人员是否对领域知识有敏锐的感知。这种微小的差异是很难察觉出来的,但可以日积月累、积微知著,一旦面对大规模业务系统时,就会体现出它的可贵之处。


  • 相关阅读:
    题目834-组队-nyoj20140818
    题目806-HEIHEI的心情-nyoj20140818
    如何配置:断路器Dashboard监控仪表盘
    Hystrix降级策略和超时调整
    微服务调用时的超时异常,使用feign的时候负载均衡策略的调整
    SpringCloud服务间调用:负载均衡策略调整
    微服务调用方式ribbon
    FastJson:Json树的CRUD操作方法实现
    java 面向对象String类
    java 面向对象内部类
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/10962283.html
Copyright © 2011-2022 走看看