概述
API 是一個服務的門面,就像衣裝是人的形象一樣。
優雅的 API 設計,能讓業務方使用起來倍兒爽,提升開發效率,降低維護成本;糟糕的 API 設計,則讓業務方遭心,陷入混沌。
本文將展示一個擴展搜索 API 的優化過程,從中也可以學到一些東西。
現狀
找一個上游工程的擴展搜索代碼如下:
extendKeywords.add((EsCondition) ConditionFactory.in("order_tags", Arrays.asList("IS_XXX_ORDER")));
extendKeywords.add(new EsCondition("goods_title", Op.match, new Match(goodsTitle, "100%")));
啊啊,真是丑死了 ! 為什么呢 ?
- 強制類型轉換。 讓業務方寫強制類型轉換,簡直是讓業務方來遭罪的 ! 這 API 設計簡直了 !
- 暴漏底層細節。 讓業務方 new EsCondition ,不僅是暴漏底層細節,還很難看!
- 不方便的傳值。 為了傳一個 in 的數值,需要寫個 Arrays.asList(e) !
- 遍布的 EsCondition 。 由於 API 設計的很裸,導致上游工程到處彌漫着 EsCondition 的煙霧。
emmm... 其實是我設計的 API 造的孽 ! 解鈴還須系鈴人。
優化過程
工廠方法
這種硬的 new EsConditon ,完全可以通過工廠方法和方法重載來消除。此外,為了收攏擴展搜索條件的構建,可以構建一個專門的 ExtendSearchParam 。
public class ExtendSearchParam implements Serializable {
private static final long serialVersionUID = 2824767430430079287L;
private List<EsCondition> extendConditions = new ArrayList<>();
public ExtendSearchParam addEq(String field, Object value) {
extendConditions.add(new EsCondition(field, Op.eq, value));
return this;
}
public ExtendSearchParam addIn(String field, Object... list) {
extendConditions.add(new EsCondition(field, Op.in, list));
return this;
}
public ExtendSearchParam addRange(String field, long gte, long lte) {
extendConditions.add(new EsCondition(field, Op.range, new Range(gte, lte)));
return this;
}
public ExtendSearchParam addMatch(String field, String query) {
extendConditions.add(new EsCondition(field, Op.match, new Match(query, "100%")));
return this;
}
public ExtendSearchParam addMatch(String field, String query, String match) {
extendConditions.add(new EsCondition(field, Op.match, new Match(query, match)));
return this;
}
}
這里借鑒了 Builder 模式,能夠鏈式地構建擴展搜索條件。這樣,業務方就可以舒心地寫上:
extendSearchParam.addIn(ORDER_TAGS, "IS_XXX_ORDER").addMatch("goods_title", goodsTitle);
沒有了類型強制轉換,沒有了暴漏底層細節,沒有了不方便的傳值,還可以一直 add 下去, 世界多美好 !
攔路虎
很快,就遇到了攔路虎:
private List<EsCondition> dealOrderSourceCondition(String orderSource) {
List<EsCondition> result = new ArrayList();
result.add(new EsCondition(TYPE_ENTRANCE, Op.eq, "wsc"));
result.add(new EsCondition(TYPE_PLATFORM, Op.eq, "wx"));
return result;
}
emmm , 寫這個方法的小伙伴也是好意,封裝一個方法來構建訂單來源擴展條件也是好意。不過這給API 重構帶來了一點點小小的障礙。
怎么重寫這一段呢 ? 第一想到的是,在 dealOrderSourceCondition 的方法里額外增加一個參數 ExtendSearchParam ,傳進去,修改它。也能達到目地。但是,—— 破壞了“不可變”原則。 不可變原則要求,盡可能避免修改入參。修改入參這種行為,很容易導致不起眼的 BUG ,如果在關鍵流程中做這個事情,有可能導致故障。有線上教訓的。
怎么辦呢 ? 又不能修改 dealOrderSourceCondition 的入參,又要把這個方法的擴展搜索條件合並到已有的擴展搜索對象中。
有一種辦法 ! 合並擴展搜索對象 ExtendSearchParam 。 這樣,ExtendSearchParam 需要支持一個合並操作:
public ExtendSearchParam merge(ExtendSearchParam extendSearchParam) {
if (extendSearchParam != null && extendSearchParam.has()) {
extendConditions.addAll(extendSearchParam.getUnmodifiedExtendSearch());
}
return extendSearchParam;
}
這樣,將 dealOrderSourceCondition 的返回值改為 ExtendSearchParam 對象,就能使用 merge 方法來合並擴展搜索條件了。
Yeap ! 想一想,除了 合並操作,還需要支持哪些操作呢 ?API 設計需要考慮周全,可不能遇到一個問題加一個支持啊 !
輔助方法
為了與原來的 OrderSearchParam 聯合使用, 需要加一些輔助方法,比如:
public boolean has() {
return CollectionUtils.isNotEmpty(extendConditions);
}
public List<EsCondition> getUnmodifiedExtendSearch() {
return Collections.unmodifiableList(extendConditions);
}
小結
本文講解了一個擴展搜索 API 的優化過程。好的 API 設計能提升業務方的使用體驗,降低維護成本。設計優雅的 API ,需要掌握一些技巧:工廠方法、重載方法、常用操作等。
從工作中不斷發現需要優化的地方,掌握方法和技巧去解決, 也是一種提升技能的方式。