将通用性的技术逻辑与差异性的业务逻辑相分离

背景

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

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

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

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

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

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

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

  • 业务逻辑: 通常是差异性的处理,比如商品列表与商品标题(可能是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 的类,才会让开发人员意识到其根本目的所在。

不同的写法,表面上是编程表达的不同,实际上体现的是设计思维和对领域知识的敏感性。 这种微小的差异是很难察觉出来的,但可以日积月累、积微知著,一旦面对大规模业务系统时,就会体现出它的可贵之处。

设计思维

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

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

远方,即在足下。


领域思维

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

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


小结

将通用技术逻辑与差异业务逻辑分离,实现了一箭三雕:凸显业务语义 ;代码复用;沉淀领域知识。 编程表达的小小差异,不仅仅体现出设计思维,还体现了一个开发人员是否对领域知识有敏锐的感知。


猜你喜欢

转载自www.cnblogs.com/lovesqcc/p/10962283.html