歡迎光臨
每天分享高質量文章

基於介面設計與程式設計

(點選上方公眾號,可快速關註)


來源:琴水玉 ,

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技能

贊(0)

分享創造快樂