將通用性的技術邏輯與差別性的業務邏輯相分離

背景

不少業務代碼,將通用性的技術邏輯與差別性的業務邏輯混雜在一塊兒,這樣的作法致使:java

  • 業務意圖不容易很快識別出來,或者要費力思考業務語義;
  • 當要複用相同的處理邏輯時,則不得不復制一份。

開發人員經常要花更多的力氣去理解業務代碼,隱形之中增長了不少理解與維護成本。遺憾的是,不少開發人員還並無充分意識到這一點,甚至以爲這無傷大雅。編程

實際上,這個細微的問題,反映的倒是開發人員廣泛缺少設計性的思考。 若是一個開發人員重視設計思惟,他就會發現,這不只僅是編程細節,而是能夠運用設計去掌控的事情。

工具

那麼,如何將通用性的技術邏輯與差別性的業務邏輯相分離呢? 首先,要明白什麼是技術邏輯,什麼是業務邏輯。設計

好比:遍歷一個商品對象列表,取出全部商品的標題列表。code

  • 技術邏輯:一般是一些通用處理,好比遍歷一個對象列表,取出對象中的某個屬性。 我並不關心是什麼對象列表,或對象的什麼屬性。 我只關心遍歷列表及如何取出對象中的某個屬性。 技術邏輯一般是須要對客戶端屏蔽的底層細節。對象

  • 業務邏輯: 一般是差別性的處理,好比商品列表與商品標題(多是JSON中的某個字段)。 我並不關心如何遍歷列表或取出對象屬性的技術細節,我只關心商品及商品標題。業務邏輯一般是領域須要重點關注的領域知識,要清晰地凸顯出來。開發

下面,將以一個示例來講明,如何將將通用性的技術邏輯與差別性的業務邏輯相分離。

get

示例

請看下面這段從主流程裏抽出來的代碼片斷。代碼清單一:io

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 是什麼意思呢? 你得去找做者溝通一下了。

class

這段代碼是將技術邏輯和業務邏輯混雜在一塊兒的典型例子。對於業務語義來講,實際上並不關心 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 的類,纔會讓開發人員意識到其根本目的所在。

應當深思語言特性提供的根本目的,並加以適當包裝,以便讓客戶端用更清晰、易懂、便利的方式去表達現實流程和規則,而不是貪圖品嚐一下新鮮特性。


設計思惟

爲何說體現了設計思惟呢?

可擴展性設計,本質上是將變化與不變分離。 技術邏輯表明了通用不變的部分,而業務邏輯表明了複雜多變的部分。將通用性的技術邏輯與差別性的業務邏輯相分離,本質上體現了可擴展性設計思惟。雖然微小的編程問題並不足以體現其優點,但確確實實能很好地鍛鍊可擴展性設計思惟。這種思惟能夠經過每一次的「將通用性的技術邏輯與差別性的業務邏輯相分離」的思考和實踐進行強化。

遠方,即在足下。


領域思惟

領域思惟,其實是一個附加效果。當把通用性的技術邏輯提煉出來後,就能更清晰地看到所要表達的內容,即:領域知識和領域規則。這能讓開發人員更容易意識到真正的關注點所在,而不是沉溺於技術和編程細節。

同時,當清晰地看到領域知識和領域規則的時候,就會思考將它放置在合適的位置,好比實體能力或領域服務, 而不是淹沒在流程代碼裏。


小結

將通用性的技術邏輯與差別性的業務邏輯分離,實現了一箭三雕:凸顯業務語義 ;代碼複用;沉澱領域知識。 編程表達的小小差別,不只僅體現出設計思惟,還體現了一個開發人員是否對領域知識有敏銳的感知。這種微小的差別是很難察覺出來的,但能夠日積月累、積微知著,一旦面對大規模業務系統時,就會體現出它的難得之處。

相關文章
相關標籤/搜索