可能不少開發者對「基於接口編程」的準則耳熟能詳,也自覺不自覺地遵照着這條準則,可並非真正明白爲何要這麼作。大部分時候,咱們定義Control, Service, Dao 接口,實際上卻不多提供超過兩個類的實現。 彷佛只是照搬準則,過分設計,並未起實際效用。不過,基於接口設計與編程,在一般情形下能夠加強方法的通用性;而在特定場景下,則能夠有助於系統更好地重構和精煉。java
當須要從一個系統提煉出更通用的系統,或者重構出一個新的系統時,預先設計的接口就會起大做用。編程
舉個例子吧, 假設如今已經有一個訂單導出的實現,以下所示:json
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() { // 實際須要考慮併發, 或者經過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 exportId return "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; }
能夠看到,幾乎全部的方法都是基於實現類來完成的。 若是這個系統就只須要訂單導出也沒什麼問題,但是,若是你想提煉出一個更通用的導出,而這個導出的流程與訂單導出很是類似,就尷尬了: 沒法複用已有的代碼和流程。它的代碼相似這樣:併發
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() { // 實際須要考慮併發, 或者經過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 exportId return "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 }
能夠看到,檢測基本的參數、保存導出記錄,生成並提交導出任務,流程及實現幾乎同樣,但是因爲以前方法限制傳入請求的實現類,使得以前的方法都沒法複用,進一步致使大段大段的重複代碼, 而若在原有基礎上改造,則要冒破壞現有系統邏輯和實現的很大風險,真是進退兩難。框架
怎麼解決呢? 最好可以預先作好設計,設計出基於接口的導出流程框架,而後編寫所須要實現的方法。ide
因爲傳遞具體的請求類限制了複用,所以,須要設計一個請求類的接口,能夠獲取通用導出參數, 具體的請求類實現該接口:設計
public interface IExportRequest { // common methods to be implemented } class OrderExportRequest implements IExportRequest { // implementations for IExportRequest methods } class GeneralExportRequest implements IExportRequest { // implementations for IExportRequest methods }
接着,基於導出請求接口,實現抽象的導出流程骨架,以下所示:code
package zzz.study.inf; import com.alibaba.fastjson.JSON; import java.util.concurrent.ExecutorService; /** * Created by shuqin on 18/3/29. */ public abstract class AbstractExportService { public String export(IExportRequest exportRequest) { checkCommon(exportRequest); checkBizParam(exportRequest); String exportId = save(exportRequest); generateJobFor(exportRequest); return exportId; } private String save(IExportRequest exportRequest) { // save export request param into db // return exportId System.out.println("save export request successfully."); return "123"; } private void generateJobFor(IExportRequest exportRequest) { getExecutor().execute(() -> exportFor(exportRequest)); System.out.println("submit export job successfully."); } public void exportFor(IExportRequest exportRequest) { // export for orderExportRequest System.out.println("export for export request for" + JSON.toJSONString(exportRequest)); } private void checkCommon(IExportRequest exportRequest) { // check bizType // check source // check templateId // check shopId System.out.println("check common request passed."); } public abstract void checkBizParam(IExportRequest exportRequest); public abstract ExecutorService getExecutor(); }
而後,就能夠實現具體的導出了:對象
訂單導出的實現以下:接口
public class OrderExportService extends AbstractExportService { ExecutorService es = Executors.newCachedThreadPool(); @Override public void checkBizParam(IExportRequest exportRequest) { System.out.println("check order export request"); } @Override public ExecutorService getExecutor() { return es; } }
通用導出的實現以下:
public class GeneralExportService extends AbstractExportService { ExecutorService es = Executors.newFixedThreadPool(10); @Override public void checkBizParam(IExportRequest exportRequest) { System.out.println("check general export request"); } @Override public ExecutorService getExecutor() { return es; } }
定義導出服務工廠,來獲取導出服務實例。在實際應用中,一般經過Spring組件注入和管理的方式實現的。
public class ExportServiceFactory { private static OrderExportService orderExportService; private static GeneralExportService generalExportService; public static AbstractExportService getExportService(IExportRequest exportRequest) { if (exportRequest instanceof OrderExportRequest) { return getOrderExportServiceInstance(); } if (exportRequest instanceof GeneralExportRequest) { return getGeneralExportServiceInstance(); } throw new IllegalArgumentException("Invalid export request type" + exportRequest.getClass().getName()); } public static OrderExportService getOrderExportServiceInstance() { // 實際須要考慮併發, 或者經過Spring容器管理實例 if (orderExportService != null) { return orderExportService; } return new OrderExportService(); } public static GeneralExportService getGeneralExportServiceInstance() { // 實際須要考慮併發, 或者經過Spring容器管理實例 if (generalExportService != null) { return generalExportService; } return new GeneralExportService(); } }
如今,能夠在客戶端使用已有的導出實現了。
public class ExportInstance { public static void main(String[] args) { OrderExportRequest orderExportRequest = new OrderExportRequest(); ExportServiceFactory.getExportService(orderExportRequest).export(orderExportRequest); GeneralExportRequest generalExportRequest = new GeneralExportRequest(); ExportServiceFactory.getExportService(generalExportRequest).export(generalExportRequest); } }
如今,訂單導出與通用導出可以複用相同的導出流程及導出方法了。
以上是模板方法模式的一個示例,闡述基於接口設計與編程的一種方法。
基於接口設計的主要場景是:1. 須要從系統中提煉出更通用的系統; 2. 須要從老系統重構出新的系統而不須要作「劇烈的變動」。有同窗可能擔憂,基於接口設計系統是否顯得「過分設計」。在我看來,先設計系統的接口骨架,可讓系統的流程更加清晰天然,更容易理解,也更容易變動和維護。接口及交互設計得足夠好,就能更好滴接近「開閉原則」,有需求變動的時候,只是新增代碼而不修改原有代碼。
基於接口設計須要有更強的總體設計思惟,預先思考和創建系統的總體行爲規約及交互,而不是走一步看一步。
JDK集合框架是基於接口設計的典範,讀者可仔細體味。
基於接口編程有三個實際層面:基於Interface編程;基於泛型接口編程; 基於Function編程。
基於Interface編程,可以讓方法不侷限於具體類,更好滴運用到多態,適配不一樣的對象實例; 基於泛型編程,可以讓方法不侷限於具體類型,可以適配更多類型;基於Function編程,可以讓方法不侷限於具體行爲,可以根據傳入的行爲而改變其具體功能變化多樣,解耦外部依賴。
經過一個實際的例子闡述了基於接口設計與編程的原因。基於接口設計與編程,可使系統更加清晰而容易擴展和變動。