設計模式的優雅:函數式pipeline+策略模式實現復雜業務@落雨


現在有一個新需求,要求對老接口進行升級,原有邏輯基礎上做功能路由,識別老業務走老接口,命中新業務(灰度)則走新接口,且新老接口出入參煥然一新,完全不同,但是要保證原有接口出入參一致(相當於強行換輪子還不要影響線上業務,前端都無需改動)。你會怎么設計?本篇文章提供2種方式來解決

流程圖:

一、常規做法(最簡單的玩法)

增加路由處理類,分別路由到原有接口和新接口,由各自接口進行處理

if(老邏輯){
  doSomeThingOld(context);
} else {
  doSomeThingNew(context);    
}

二、優雅做法(抽象、piple、策略)

如果按照常規模式,那么這個需求就結束了,只需在入口層增加一個路由,后面各自的接口自己去實現入參校驗、參數組裝、邏輯處理、結果變換、log打印,但是你稍加思索就覺得,哎呀,這一套組合拳下來,豈不是一樣的流程,很好,我們開始抽象。

2.1 抽出函數處理器,負責執行函數

/**
* Pipe執行器,使用Stream.map處理各層Fuction
*  <pre>
     Function<ItemResultContext, ItemResultContext>> 的含義是使用一個ItemResultContext入參,傳出一個ItemResultContext出參
   </pre>
* @param context
* @param functionList
* @return
*/
private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
   List<Function<ItemResultContext, ItemResultContext>> functionList) {
   // 獲取context流,context是每個函數的出入參
   Stream<ItemResultContext> stream = Stream.of(context);
   if (CollectionUtils.isEmpty(functionList)) {
       return Optional.empty();
   }

   // 同步執行,循環處理函數流, stream1 -> stream2 -> stream3 -> ... streamN
   for (Function f : functionList) {
       // @link{<R> Stream<R> map(Function<? super T, ? extends R> mapper);}
       stream = stream.map(f);
   }

   return stream.findFirst();
}

2.2 抽出pipeline函數處理器,用來定義流程

/**
* pipe流程(pipe組裝,交給子類實現)
* @param context
* @param functionList
* @return
*/
public ItemResultContext buildItemResult(ItemResultContext context) {
        // 組裝流程
        List<Function<ItemResultContext, ItemResultContext>> list = Lists.newArrayList();
        list.add(logAroundBuildItemResult("covertReq", this::covertReq));
        list.add(logAroundBuildItemResult("queryItems", this::queryItems));
        list.add(logAroundBuildItemResult("covertItems", this::covertItems));
        list.add(logAroundBuildItemResult("queryCategorys", this::queryCategorys));
        list.add(logAroundBuildItemResult("combineResult", this::combineResult));
        Optional<ItemResultContext> opt = buildItemResult(context, list);
        return opt.orElse(context);
    }

2.3 抽出log處理,環繞切面,切函數

/**
* Pipe環繞切面,apply -> function
* @param desc
* @param func
* @return
*/
private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
   Function<ItemResultContext, ItemResultContext> func) {
   return req -> {
       long start = System.currentTimeMillis();
       log.error("[{}] start", desc);
       ItemResultContext resp = func.apply(req);
       log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
       return resp;
   };
}

2.4 將此函數處理器、切面環繞等基礎方法抽到父類,提取抽象方法,讓子類來實現函數pipe組裝,讓子類來實現更多的業務場景

@Slf4j
public abstract class AbstractItemManager implements ItemManager {

    /**
     * 獲取結果(pipe組裝,交給子類實現)
     * @param context
     * @return
     */
    public abstract ItemResultContext buildItemResult(ItemResultContext context);

    /**
     * 轉換req到上下文
     * @param context
     * @return
     */
    public abstract ItemResultContext covertReq(ItemResultContext context);

    /**
     * 查詢商品
     * @param context
     * @return
     */
    public abstract ItemResultContext queryItems(ItemResultContext context);

    /**
     * 轉換結果
     * @param context
     * @return
     */
    public abstract ItemResultContext covertItems(ItemResultContext context);

    /**
     * 查詢類目
     * @param context
     * @return
     */
    public abstract ItemResultContext queryCategorys(ItemResultContext context);

/**
     * Pipe執行器
     * @param context
     * @param functionList
     * @return
     */
    private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
        List<Function<ItemResultContext, ItemResultContext>> functionList) {
        // 獲取context
        Stream<ItemResultContext> stream = Stream.of(context);
        if (CollectionUtils.isEmpty(functionList)) {
            return Optional.empty();
        }

        // 函數列表執行,同步執行
        for (Function f : functionList) {
            stream = stream.map(f);
        }

        return stream.findFirst();
    }

    /**
     * Pipe環繞切面,apply -> function
     * @param desc
     * @param func
     * @return
     */
    private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
        Function<ItemResultContext, ItemResultContext> func) {
        return req -> {
            long start = System.currentTimeMillis();
            log.error("[{}] start", desc);
            ItemResultContext resp = func.apply(req);
            log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
            return resp;
        };
    }
}

2.5 完事,很清爽.

有兄弟問我,ItemResultContext是干啥的,這個里面就是個上下文對象,里面可以放Anything,比如入參、出參對象等,方便Function執行的時候(Function<ItemResultContext/**入參類型T**/, ItemResultContext>/**出參類型T**/),上下傳遞,是統一的最外層容器。

@Data
public class ItemResultContext implements Serializable {

    private static final long serialVersionUID = -1L;

    /**
     * 老模型上下文
     */
    private ItemContext itemContext;

    /**
     * 新模型上下文
     */
    private XItemContext xItemContext;
 
}

落雨 2021-09-10 20:12:45
http://www.js-dev.cn


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM