基于接口设计与编程
問題
可能很多開發(fā)者對“基于接口編程”的準則耳熟能詳,也自覺不自覺地遵守著這條準則,可并不是真正明白為什么要這么做。大部分時候,我們定義Control, Service, Dao 接口,實際上卻很少提供超過兩個類的實現(xiàn)。 似乎只是照搬準則,過度設計,并未起實際效用。不過,基于接口設計與編程,在通常情形下可以增強方法的通用性;而在特定場景下,則可以有助于系統(tǒng)更好地重構和精煉。
當需要從一個系統(tǒng)提煉出更通用的系統(tǒng),或者重構出一個新的系統(tǒng)時,預先設計的接口就會起大作用。
舉個例子吧, 假設現(xiàn)在已經有一個訂單導出的實現(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)就只需要訂單導出也沒什么問題,可是,如果你想提煉出一個更通用的導出,而這個導出的流程與訂單導出非常相似,就尷尬了: 無法復用已有的代碼和流程。它的代碼類似這樣:
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ù)、保存導出記錄,生成并提交導出任務,流程及實現(xiàn)幾乎一樣,可是由于之前方法限制傳入請求的實現(xiàn)類,使得之前的方法都無法復用,進一步導致大段大段的重復代碼, 而若在原有基礎上改造,則要冒破壞現(xiàn)有系統(tǒng)邏輯和實現(xiàn)的很大風險,真是進退兩難。
解決
怎么解決呢? 最好能夠預先做好設計,設計出基于接口的導出流程框架,然后編寫所需要實現(xiàn)的方法。
定義接口
由于傳遞具體的請求類限制了復用,因此,需要設計一個請求類的接口,可以獲取通用導出參數(shù), 具體的請求類實現(xiàn)該接口:
實現(xiàn)抽象導出
接著,基于導出請求接口,實現(xiàn)抽象的導出流程骨架,如下所示:
具體導出
然后,就可以實現(xiàn)具體的導出了:
訂單導出的實現(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;} }
通用導出的實現(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;} }
導出服務工廠
定義導出服務工廠,來獲取導出服務實例。在實際應用中,通常通過Spring組件注入和管理的方式實現(xiàn)的。
客戶端使用
現(xiàn)在,可以在客戶端使用已有的導出實現(xiàn)了。
現(xiàn)在,訂單導出與通用導出能夠復用相同的導出流程及導出方法了。
基于接口設計
以上是模板方法模式的一個示例,闡述基于接口設計與編程的一種方法。
基于接口設計的主要場景是:1. 需要從系統(tǒng)中提煉出更通用的系統(tǒng); 2. 需要從老系統(tǒng)重構出新的系統(tǒng)而不需要做“劇烈的變更”。有同學可能擔心,基于接口設計系統(tǒng)是否顯得“過度設計”。在我看來,先設計系統(tǒng)的接口骨架,可以讓系統(tǒng)的流程更加清晰自然,更容易理解,也更容易變更和維護。接口及交互設計得足夠好,就能更好滴接近“開閉原則”,有需求變更的時候,只是新增代碼而不修改原有代碼。
基于接口設計需要有更強的整體設計思維,預先思考和建立系統(tǒng)的整體行為規(guī)約及交互,而不是走一步看一步。
JDK集合框架是基于接口設計的典范,讀者可仔細體味。
基于接口編程
基于接口編程有三個實際層面:基于Interface編程;基于泛型接口編程; 基于Function編程。
基于Interface編程,能夠讓方法不局限于具體類,更好滴運用到多態(tài),適配不同的對象實例; 基于泛型編程,能夠讓方法不局限于具體類型,能夠適配更多類型;基于Function編程,能夠讓方法不局限于具體行為,能夠根據(jù)傳入的行為而改變其具體功能變化多樣,解耦外部依賴。
小結
通過一個實際的例子闡述了基于接口設計與編程的緣由。基于接口設計與編程,可以使系統(tǒng)更加清晰而容易擴展和變更。
作者:@琴水玉
鏈接:http://www.cnblogs.com/lovesqcc/p/8672868.html
轉載于:https://blog.51cto.com/13672582/2304951
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
- 上一篇: 【Objective-C学习笔记】变量和
- 下一篇: kubernetes node节点失效