(點選上方公眾號,可快速關註)
來源:琴水玉 ,
www.cnblogs.com/lovesqcc/p/8672868.html
問題
可能很多開發者對“基於介面程式設計”的準則耳熟能詳,也自覺不自覺地遵守著這條準則,可並不是真正明白為什麼要這麼做。大部分時候,我們定義Control, Service, Dao 介面,實際上卻很少提供超過兩個類的實現。 似乎只是照搬準則,過度設計,並未起實際效用。不過,基於介面設計與程式設計,在通常情形下可以增強方法的通用性;而在特定場景下,則可以有助於系統更好地重構和精煉。
當需要從一個系統提煉出更通用的系統,或者重構出一個新的系統時,預先設計的介面就會起大作用。
舉個例子吧, 假設現在已經有一個訂單匯出的實現,如下所示:
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().export(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
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().export(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
}
可以看到,檢測基本的引數、儲存匯出記錄,生成並提交匯出任務,流程及實現幾乎一樣,可是由於之前方法限制傳入請求的實現類,使得之前的方法都無法復用,進一步導致大段大段的重覆程式碼, 而若在原有基礎上改造,則要冒破壞現有系統邏輯和實現的很大風險,真是進退兩難。
解決
怎麼解決呢? 最好能夠預先做好設計,設計出基於介面的匯出流程框架,然後編寫所需要實現的方法。
定義介面
由於傳遞具體的請求類限制了復用,因此,需要設計一個請求類的介面,可以獲取通用匯出引數, 具體的請求類實現該介面:
public interface IExportRequest {
// common methods to be implemented
}
class OrderExportRequest implements IExportRequest {
// implementations for IExportRequest methods
}
class GeneralExportRequest implements IExportRequest {
// implementations for IExportRequest methods
}
實現抽象匯出
接著,基於匯出請求介面,實現抽象的匯出流程骨架,如下所示:
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;
}
}
通用匯出的實現如下:
ublic 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程式設計,能夠讓方法不侷限於具體行為,能夠根據傳入的行為而改變其具體功能變化多樣,解耦外部依賴。
小結
透過一個實際的例子闡述了基於介面設計與程式設計的緣由。基於介面設計與程式設計,可以使系統更加清晰而容易擴充套件和變更。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能