基于接口设计与编程

问题

可能很多开发者对“基于接口编程”的准则耳熟能详,也自觉不自觉地遵守着这条准则,可并不是真正明白为什么要这么做。大部分时候,我们定义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().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
 
}

可以看到,检测基本的参数、保存导出记录,生成并提交导出任务,流程及实现几乎一样,可是由于之前方法限制传入请求的实现类,使得之前的方法都无法复用,进一步导致大段大段的重复代码, 而若在原有基础上改造,则要冒破坏现有系统逻辑和实现的很大风险,真是进退两难。

解决
怎么解决呢? 最好能够预先做好设计,设计出基于接口的导出流程框架,然后编写所需要实现的方法。

定义接口
由于传递具体的请求类限制了复用,因此,需要设计一个请求类的接口,可以获取通用导出参数, 具体的请求类实现该接口:

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;
  }
}

通用导出的实现如下:

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编程,能够让方法不局限于具体行为,能够根据传入的行为而改变其具体功能变化多样,解耦外部依赖。

小结

通过一个实际的例子阐述了基于接口设计与编程的缘由。基于接口设计与编程,可以使系统更加清晰而容易扩展和变更。

作者:@琴水玉

链接:http://www.cnblogs.com/lovesqcc/p/8672868.html

更多干货内容可关注二维码后回复“干货”即可获取

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容

  • 秋天是一个丰收的季节。美丽又美味的韭花也应时开放了。何不换换口味、自己动手做个韭花酱? 1、先把韭花、鲜姜、...
    林中小屋123阅读 1,008评论 0 2
  • 儿时,我曾经迷恋一把小锤子,那其实只是一根奇形的金属条。上课的时候都带着它。无论看到什么,都用它敲击。后来不慎弄断...
    bookaa阅读 510评论 0 2
  • 昨天上午清宝喊着去审批中心,在那里呆了2个小时,真的用于协调工作,不过15分钟。在那里闲坐,又像吸鸦片上瘾一样忍不...
    雪木912阅读 417评论 0 0