基於接口設計與編程

問題

可能不少開發者對「基於接口編程」的準則耳熟能詳,也自覺不自覺地遵照着這條準則,可並非真正明白爲何要這麼作。大部分時候,咱們定義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編程,可以讓方法不侷限於具體行爲,可以根據傳入的行爲而改變其具體功能變化多樣,解耦外部依賴。

小結

經過一個實際的例子闡述了基於接口設計與編程的原因。基於接口設計與編程,可使系統更加清晰而容易擴展和變動。

相關文章
相關標籤/搜索