基于接口设计与编程
問題
可能很多開發(fā)者對“基于接口編程”的準(zhǔn)則耳熟能詳,也自覺不自覺地遵守著這條準(zhǔn)則,可并不是真正明白為什么要這么做。大部分時候,我們定義Control, Service, Dao 接口,實際上卻很少提供超過兩個類的實現(xiàn)。 似乎只是照搬準(zhǔn)則,過度設(shè)計,并未起實際效用。不過,基于接口設(shè)計與編程,在通常情形下可以增強(qiáng)方法的通用性;而在特定場景下,則可以有助于系統(tǒng)更好地重構(gòu)和精煉。
當(dāng)需要從一個系統(tǒng)提煉出更通用的系統(tǒng),或者重構(gòu)出一個新的系統(tǒng)時,預(yù)先設(shè)計的接口就會起大作用。
舉個例子吧, 假設(shè)現(xiàn)在已經(jīng)有一個訂單導(dǎo)出的實現(xiàn),如下所示:
package zzz.study.inf;import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.Data;/*** Created by shuqin on 18/3/29.*/ public class OrderExportService {private static OrderExportService orderExportService;ExecutorService es = Executors.newFixedThreadPool(10);public static void main(String[] args) {getInstance().exportOrder(new OrderExportRequest());}public static OrderExportService getInstance() {// 實際需要考慮并發(fā), 或者通過Spring容器管理實例if (orderExportService != null) {return orderExportService;}return new OrderExportService();}public String exportOrder(OrderExportRequest orderExportRequest) {check(orderExportRequest);String exportId = save(orderExportRequest);generateJobFor(orderExportRequest);return exportId;}private String save(OrderExportRequest orderExportRequest) {// save export request param into db// return exportIdreturn "123";}private void generateJobFor(OrderExportRequest orderExportRequest) {es.execute(() -> exportFor(orderExportRequest));}private void exportFor(OrderExportRequest orderExportRequest) {// export for orderExportRequest}private void check(OrderExportRequest orderExportRequest) {// check bizType// check source// check templateId// check shopId// check biz params}}@Data class OrderExportRequest {private String bizType;private String source;private String templateId;private String shopId;private String orderNos;private List<String> orderStates;}
可以看到,幾乎所有的方法都是基于實現(xiàn)類來完成的。 如果這個系統(tǒng)就只需要訂單導(dǎo)出也沒什么問題,可是,如果你想提煉出一個更通用的導(dǎo)出,而這個導(dǎo)出的流程與訂單導(dǎo)出非常相似,就尷尬了: 無法復(fù)用已有的代碼和流程。它的代碼類似這樣:
package zzz.study.inf;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import lombok.Data;/*** Created by shuqin on 18/3/29.*/ public class GeneralExportService {private static GeneralExportService generalExportService;ExecutorService es = Executors.newFixedThreadPool(10);public static void main(String[] args) {getInstance().exportOrder(new GeneralExportRequest());}public static GeneralExportService getInstance() {// 實際需要考慮并發(fā), 或者通過Spring容器管理實例if (generalExportService != null) {return generalExportService;}return new GeneralExportService();}public String exportOrder(GeneralExportRequest generalExportRequest) {check(generalExportRequest);String exportId = save(generalExportRequest);generateJobFor(generalExportRequest);return exportId;}private String save(GeneralExportRequest generalExportRequest) {// save export request param into db// return exportIdreturn "123";}private void generateJobFor(GeneralExportRequest generalExportRequest) {es.execute(() -> exportFor(generalExportRequest));}private void exportFor(GeneralExportRequest orderExportRequest) {// export for orderExportRequest}private void check(GeneralExportRequest generalExportRequest) {// check bizType// check source// check templateId// check shopId// check general params}}@Data class GeneralExportRequest {private String bizType;private String source;private String templateId;private String shopId;// general export param}
可以看到,檢測基本的參數(shù)、保存導(dǎo)出記錄,生成并提交導(dǎo)出任務(wù),流程及實現(xiàn)幾乎一樣,可是由于之前方法限制傳入請求的實現(xiàn)類,使得之前的方法都無法復(fù)用,進(jìn)一步導(dǎo)致大段大段的重復(fù)代碼, 而若在原有基礎(chǔ)上改造,則要冒破壞現(xiàn)有系統(tǒng)邏輯和實現(xiàn)的很大風(fēng)險,真是進(jìn)退兩難。
解決
怎么解決呢? 最好能夠預(yù)先做好設(shè)計,設(shè)計出基于接口的導(dǎo)出流程框架,然后編寫所需要實現(xiàn)的方法。
定義接口
由于傳遞具體的請求類限制了復(fù)用,因此,需要設(shè)計一個請求類的接口,可以獲取通用導(dǎo)出參數(shù), 具體的請求類實現(xiàn)該接口:
實現(xiàn)抽象導(dǎo)出
接著,基于導(dǎo)出請求接口,實現(xiàn)抽象的導(dǎo)出流程骨架,如下所示:
具體導(dǎo)出
然后,就可以實現(xiàn)具體的導(dǎo)出了:
訂單導(dǎo)出的實現(xiàn)如下:
public class OrderExportService extends AbstractExportService {ExecutorService es = Executors.newCachedThreadPool();@Overridepublic void checkBizParam(IExportRequest exportRequest) {System.out.println("check order export request");}@Overridepublic ExecutorService getExecutor() {return es;} }
通用導(dǎo)出的實現(xiàn)如下:
public class GeneralExportService extends AbstractExportService {ExecutorService es = Executors.newFixedThreadPool(10);@Overridepublic void checkBizParam(IExportRequest exportRequest) {System.out.println("check general export request");}@Overridepublic ExecutorService getExecutor() {return es;} }
導(dǎo)出服務(wù)工廠
定義導(dǎo)出服務(wù)工廠,來獲取導(dǎo)出服務(wù)實例。在實際應(yīng)用中,通常通過Spring組件注入和管理的方式實現(xiàn)的。
客戶端使用
現(xiàn)在,可以在客戶端使用已有的導(dǎo)出實現(xiàn)了。
現(xiàn)在,訂單導(dǎo)出與通用導(dǎo)出能夠復(fù)用相同的導(dǎo)出流程及導(dǎo)出方法了。
基于接口設(shè)計
以上是模板方法模式的一個示例,闡述基于接口設(shè)計與編程的一種方法。
基于接口設(shè)計的主要場景是:1. 需要從系統(tǒng)中提煉出更通用的系統(tǒng); 2. 需要從老系統(tǒng)重構(gòu)出新的系統(tǒng)而不需要做“劇烈的變更”。有同學(xué)可能擔(dān)心,基于接口設(shè)計系統(tǒng)是否顯得“過度設(shè)計”。在我看來,先設(shè)計系統(tǒng)的接口骨架,可以讓系統(tǒng)的流程更加清晰自然,更容易理解,也更容易變更和維護(hù)。接口及交互設(shè)計得足夠好,就能更好滴接近“開閉原則”,有需求變更的時候,只是新增代碼而不修改原有代碼。
基于接口設(shè)計需要有更強(qiáng)的整體設(shè)計思維,預(yù)先思考和建立系統(tǒng)的整體行為規(guī)約及交互,而不是走一步看一步。
JDK集合框架是基于接口設(shè)計的典范,讀者可仔細(xì)體味。
基于接口編程
基于接口編程有三個實際層面:基于Interface編程;基于泛型接口編程; 基于Function編程。
基于Interface編程,能夠讓方法不局限于具體類,更好滴運用到多態(tài),適配不同的對象實例; 基于泛型編程,能夠讓方法不局限于具體類型,能夠適配更多類型;基于Function編程,能夠讓方法不局限于具體行為,能夠根據(jù)傳入的行為而改變其具體功能變化多樣,解耦外部依賴。
小結(jié)
通過一個實際的例子闡述了基于接口設(shè)計與編程的緣由?;诮涌谠O(shè)計與編程,可以使系統(tǒng)更加清晰而容易擴(kuò)展和變更。
作者:@琴水玉
鏈接:http://www.cnblogs.com/lovesqcc/p/8672868.html
轉(zhuǎn)載于:https://blog.51cto.com/13672582/2304951
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
- 上一篇: 【Objective-C学习笔记】变量和
- 下一篇: kubernetes node节点失效