副标题:为什么基础设施用抽象类,平台差异用接口,模板方法用组合?
📌 「Java面试·实战笔记」系列第 2 篇
上一篇 我用大白话比喻,帮大家彻底搞懂了接口和抽象类的基础区别,没看过的朋友可以跳转第一篇:【接口和抽象类有什么区别?电子面单多平台对接实战】。但说实话,背概念没用,落地到项目才是真本事。
今天这篇,我直接拿线上真实落地的多平台电子面单生产架构跟大家拆解,全程无虚构代码、无空洞八股文。
重点讲透三个核心问题:
-
项目里的 DefaultBaseManager,为啥一定要用抽象类?
-
多平台差异化逻辑,为啥拆成三层策略接口,不用抽象类?
-
模板方法模式,为啥抛弃传统继承,改用组合方式实现?
看完这篇,你以后面试被问“接口和抽象类怎么选型”,再也不用死记硬背,直接套生产落地逻辑回答,“吊打”大部分八股文选手。
文章目录
1 先复盘:别停留在“背概念”,要落地到“写代码”
上一篇我们总结了最基础的选型逻辑,这里快速回顾一下:
-
抽象类:代表 is-a 父子关系,适合共享状态、通用模板、公共基础设施能力
-
接口:代表 can-do 能力契约,适合解耦、多态、隔离业务差异
之前的示例是简化版demo,今天咱们直接上真实业务架构——多平台电子面单系统。
这个系统要对接奇门、抖音、京东、支付宝等十几家电商平台,每个平台的请求、响应、异常规则全都不一样,非常适合用来讲接口、抽象类、组合模式的精准选型。
2 真实架构全景速览
先看精简后的分层架构图,一眼看懂整体设计:

整个架构的核心规律,一句话总结:
上层通用基础设施,用抽象类;下层平台差异化业务,用接口;固定流程模板,用组合实现。
下面逐层拆开讲,每一个选型都告诉你“为什么这么写,这么写的好处是什么”。
3 抽象类:专门用来做「基础设施复用」
3.1 生产真实代码:DefaultBaseManager
我们项目里所有的业务Service,全部统一继承 DefaultBaseManager:
不管是订单修改服务、面单取号服务,都能直接复用父类的公共能力,不用每个类重复定义。
public class TocOrderExpressModifyService extends DefaultBaseManager {
public BatchResult editTocLogistic(List<Long> ids, String express, String productCode) {
// 直接调用父类事务方法,不用自己写事务逻辑
TransactionStatus status = beginTxPropagationRequiresNew();
// ... 业务逻辑
commitTransaction(status);
}
}
public class WaybillFetchService extends DefaultBaseManager {
public boolean fetchWaybill(TocWmsPickTicket ticket, int exsitJianNum, String productCode) {
// 直接用父类的DAO、日志对象
ctx.getExt().put(WaybillContext.KEY_COMMON_DAO, this.commonDao);
logger.info("开始取号...");
}
}
而这个抽象父类,统一封装了所有业务服务的通用基础设施:
-
数据库操作对象 commonDao
-
统一日志对象 logger
-
全套事务管理方法:开启事务、提交事务、回滚事务
3.2 重点:为啥这里必须用抽象类?接口不行吗?
答案:完全不行,语法和设计层面都不支持。
我给大家讲三个最核心的原因,面试直接背这三点就够了:
① 接口不能定义实例变量,无法共享状态
commonDao 是需要Spring注入的实例对象,每个子类都要共用。但接口里的变量默认是static final 常量,根本不能定义可变的实例属性。
// 编译直接报错!接口做不到状态共享
public interface BaseManager {
CommonDao commonDao;
}
② 抽象类可以写构造方法,统一管理依赖注入
所有子类的DAO、日志依赖,都可以在抽象父类统一初始化,子类直接复用,不用重复注入。接口没有构造方法,完全做不到。
③ 语义是标准的 is-a 关系
所有业务Service,本质就是一个基础业务管理器,完全符合抽象类的父子继承关系,用继承天然合理。
最终结论:
只要需要共享实例状态、封装通用基础设施、统一初始化依赖,优先用抽象类,接口替代不了。(面试直接背诵)
3.3 JDK源码佐证:大佬也是这么写的
不光我们业务项目,JDK底层核心源码全是这个思路:
-
AbstractList:持有modCount状态字段,封装集合通用逻辑
-
AbstractMap:缓存keySet、values,提供Map通用骨架方法
-
AQS抽象队列同步器:持有state、head、tail核心状态,是锁机制的基础父类
这些核心抽象类,全部都是封装通用能力、共享状态,和我们的DefaultBaseManager设计思路一模一样。
4 接口:专门用来「隔离平台业务差异」
4.1 生产核心设计:三层策略接口
多平台对接最大的痛点:每个平台的请求、响应、异常判断逻辑全都不一样。
如果写一堆if-else判断平台,代码会乱到没法维护。所以我们直接抽了三层独立接口,把所有平台差异彻底剥离:
// 1. 请求构建策略:不同平台拼参规则不同
public interface RequestStrategy {
Object buildRequest(WaybillContext ctx);
}
// 2. 响应解析策略:不同平台返回报文格式不同
public interface ParseStrategy {
List<TocPickTicketWayBillDetailsNew> parseResponse(String response, WaybillContext ctx);
}
// 3. 异常判断策略:不同平台报错字段、提示文案不同
public interface ExceptionStrategy {
boolean isBusinessSuccess(String response);
String extractErrorMsg(String response);
}
然后每个平台单独实现这三套接口,各自写自己的差异化逻辑:
// 奇门平台专属实现
public class QiMenRequestStrategy implements RequestStrategy {
@Override
public Object buildRequest(WaybillContext ctx) {
return QiMenWaybillBuilder.buildRequest(ctx);
}
}
// 抖音平台专属实现
public class DouYinRequestStrategy implements RequestStrategy {
@Override
public Object buildRequest(WaybillContext ctx) {
return buildDouYinJson(ctx);
}
}
4.2 为啥这里用接口,不用抽象类?
四个通俗好懂的理由:
① 语义是can-do能力,不是is-a父子关系
抖音、奇门的请求构建类,不需要继承某个父类,只需要具备“构建请求”的能力就行,这就是接口的核心语义:能力契约。
② 策略类无状态,不需要共享属性
所有策略实现类都没有自定义成员变量,所有参数都通过上下文传入,无状态的业务能力,用接口最轻量化。
③ 支持灵活多实现,扩展性拉满
一个平台后续如果新增取消、重打等能力,可以直接多实现多个接口,抽象类只能单继承,完全没这么灵活。
④ 完美贴合开闭原则
新增支付宝、拼多多等新平台,只需要新增实现类,不用改一行旧代码。如果用抽象类,很容易需要修改父类逻辑,破坏开闭原则。
4.3 延伸:API调度也用接口统一
我们的API调用层,同样用接口做统一约束:
public interface RequestHandler {
String handle(WaybillContext ctx, Object request, String traceId) throws IOException;
}
奇门、抖音、通用HTTP、OAuth2鉴权,各自实现这个接口,工厂根据平台编码+子渠道复合维度路由,精准匹配对应处理器,彻底避免策略覆盖bug。
5 重点进阶:模板方法为啥用组合,不用继承?
5.1 传统模板方法的坑
很多人学模板方法模式,只知道用抽象类+子类继承重写。
但在我们的生产架构里,完全抛弃了这种写法,改用 组合式模板 WaybillFetchTemplate。
5.2 生产真实组合模板代码
public class WaybillFetchTemplate {
// 直接注入三层策略接口,组合复用,不用继承
private final RequestStrategy requestStrategy;
private final ParseStrategy parseStrategy;
private final ExceptionStrategy exceptionStrategy;
private final ApiInvoker apiInvoker;
private final WaybillPersistence persistence;
// 构造函数注入所有策略,灵活组装
public WaybillFetchTemplate(RequestStrategy req, ParseStrategy parse,
ExceptionStrategy ex, ApiInvoker invoker, WaybillPersistence persist) {
this.requestStrategy = req;
this.parseStrategy = parse;
this.exceptionStrategy = ex;
this.apiInvoker = invoker;
this.persistence = persist;
}
// 固定的核心流程骨架,永不修改
public boolean execute(WaybillContext ctx) {
String traceId = ctx.getTicket().getCode() + "_" + System.currentTimeMillis();
try {
// 1. 差异化:构建请求
Object request = requestStrategy.buildRequest(ctx);
// 2. 通用:调用API
String response = apiInvoker.invoke(ctx, request, traceId);
// 3. 差异化:业务异常判断
if (!exceptionStrategy.isBusinessSuccess(response)) {
String errMsg = exceptionStrategy.extractErrorMsg(response);
markException(ctx.getTicket(), errMsg);
return false;
}
// 4. 差异化:解析响应
List<Detail> details = parseStrategy.parseResponse(response, ctx);
if (details == null || details.isEmpty()) {
markException(ctx.getTicket(), "未获取到运单号");
return false;
}
// 5. 通用:持久化数据
boolean isFirst = (ctx.getExsitJianNum() == 0);
persistence.saveAndBind(ctx.getTicket(), details, isFirst);
return true;
} catch (Exception e) {
markException(ctx.getTicket(), "系统异常: " + e.getMessage());
return false;
}
}
}
5.3 为啥组合比继承更香?4个实战理由
① 差异逻辑已经通过接口剥离完毕,没必要再继承
平台的所有差异化步骤,已经交给三层策略接口实现了。如果再搞一个抽象模板类让子类继承重写,纯属重复造轮子,代码冗余。
② 彻底避免类爆炸
如果用继承,10个平台就要写10个模板子类。用组合,一个模板类适配所有平台,只需要注入不同策略即可。
③ 运行时动态灵活
继承的类关系编译期就固定死了,组合可以在运行时通过工厂动态替换、组合策略,适配不同平台、不同渠道。
④ 测试极其简单
可以直接Mock策略接口,单独测试模板核心流程,不用启动Spring容器,不用依赖继承关系。
⑤ 严格遵循单一职责原则,代码职责高度拆分
通过组合模式将「固定流程骨架」和「差异化业务逻辑」完全拆分:WaybillFetchTemplate 只专注负责流程编排、通用逻辑处理、异常兜底,各司其职;而三层策略接口只专注各自的单一差异化能力,完全贴合单一职责原则。反观传统继承式模板方法,所有逻辑集中在父类和子类中,极易导致模板类职责臃肿、代码耦合严重,后续维护迭代成本极高。
面试金句:
模板方法的核心是“固定流程、差异化步骤”。传统继承式适合无前置解耦的场景,而我们项目中差异逻辑已通过接口独立,用组合实现更轻量、更灵活、符合单一职责。
5.4 极简可运行Demo,秒懂组合模板
下面这段代码可以直接复制运行,帮你直观理解核心思想:
public class TemplateDemo {
// 差异化策略接口
interface Strategy {
String execute();
}
// 固定模板类(组合策略,不继承)
static class Template {
private final Strategy strategy;
public Template(Strategy s) { this.strategy = s; }
public void run() {
System.out.println("1. 公共前置步骤");
System.out.println("2. " + strategy.execute());
System.out.println("3. 公共后置步骤");
}
}
public static void main(String[] args) {
// 平台A差异化逻辑
new Template(() -> "平台A:淘宝SDK签名 + 调用奇门API").run();
// 平台B差异化逻辑
new Template(() -> "平台B:MD5签名 + 调用抖音API").run();
}
}
运行结果:
1. 公共前置步骤
2. 平台A:淘宝SDK签名 + 调用奇门API
3. 公共后置步骤
1. 公共前置步骤
2. 平台B:MD5签名 + 调用抖音API
3. 公共后置步骤
核心:流程骨架固定,差异化步骤可随意替换,这就是组合模板的精髓。
6 终极选型总结(面试直接套)
| 业务场景 | 选型方案 | 生产案例 |
|---|---|---|
| 封装DAO、日志、事务等通用基础设施 | 抽象类 | DefaultBaseManager |
| 隔离各平台差异化业务逻辑 | 接口 | 三层策略接口 |
| 统一API调用能力契约 | 接口 | RequestHandler |
| 固定流程骨架,步骤已通过接口解耦 | 组合类 | WaybillFetchTemplate |
三分钟选型口诀:
-
有共享状态、通用基础设施 → 抽象类
-
无状态、纯能力契约、业务差异解耦 → 接口
-
流程固定、步骤可替换,且已接口化 → 组合优先于继承
7 生产踩坑真实教训(避坑必看)
7.1 策略路由维度不足,导致策略覆盖
早期我们只用 platFormCode 单维度缓存策略,导致抖音普通、抖音代发策略互相覆盖:
requestMap.put("DY", new DouYinRequestStrategy()); // 抖音普通
requestMap.put("DY", new DouYinDaiFaRequestStrategy()); // 直接覆盖上面的策略!
教训:同平台多子渠道,必须用 platFormCode + 子渠道标识 复合键路由,和ApiInvoker保持统一。
7.2 抽象类方法权限乱用
公共通用方法如果定义为protected,子类可以随意重写,容易绕过核心校验逻辑。
规范:公共固定流程用private/final保护,只暴露必须实现的抽象方法。
7.4 传统继承模板的耦合致命坑
这也是我们彻底抛弃继承式模板方法的核心踩坑经验:早期项目曾使用抽象模板类+子类继承的方式实现流程编排,产生了严重的继承耦合问题。
一方面,子类强依赖父类的实现逻辑,一旦父类修改通用流程、调整参数校验规则、优化异常兜底逻辑,所有继承该模板的平台子类都会被动受影响,极易引发全平台隐性bug,牵一发而动全身,线上风险极高;另一方面,子类可以随意重写父类的通用固定方法,部分开发不规范的重写,会破坏模板预设的核心流程逻辑,导致不同平台的执行流程不统一,排查问题难度极大。
核心教训:继承是静态强耦合关系,会大幅提升代码维护风险;而组合是弱依赖关系,通过注入策略实现能力组装,完全解耦核心流程与差异化逻辑,从根源规避继承耦合问题。
7.3 接口default方法菱形冲突
多个接口定义同名default方法,实现类必须强制重写,否则编译报错。开发中尽量规避同名默认方法,减少冲突。
8 延伸思考:新增支付宝平台,该怎么改代码?
大家可以先自己思考一下,再看答案,加深理解!
👋 先独立思考,再往下核对答案
参考答案
需要新增的代码(零侵入旧代码):
-
支付宝请求策略:ZFBRequestStrategy 实现 RequestStrategy
-
支付宝解析策略:ZFBParseStrategy 实现 ParseStrategy
-
支付宝异常策略:ZFBExceptionStrategy 实现 ExceptionStrategy
-
支付宝API处理器:ZFBHandler 实现 RequestHandler(或复用通用SimpleHttpHandler)
需要改动的代码:
-
策略工厂注册新策略
-
API调用器注册新处理器
完全不用动的核心代码:
模板流程、服务层逻辑全部零改动!
这就是开闭原则的终极魅力:新增功能只加代码,不改旧逻辑。
9 评论区面试挑战
面试原题:
DefaultBaseManager用抽象类,RequestStrategy用接口,请从设计语义和语法限制两个角度说明选型原因?
标准答案:
-
设计语义:DefaultBaseManager是is-a父子关系,是所有服务的基础父类;RequestStrategy是can-do能力契约,只约束“能构建请求”的行为,无继承关系。
-
语法限制:DefaultBaseManager需要持有DAO、日志等实例状态,接口无法定义实例变量;策略类无状态、纯行为定义,接口完全适配。
10 全文总结(面试绝杀一句话)
在我们多平台电子面单架构中,抽象类负责基础设施状态复用,接口负责平台业务差异解耦,模板方法通过组合实现灵活的流程编排,三者配合完美实现了“新增平台不改动核心代码”的开闭原则。
🔜 本系列完整连载阅读顺序
已更新内容
- 第1篇:《接口与抽象类到底怎么分?通俗比喻+基础选型框架》
👉 上篇直达:接口和抽象类有什么区别?电子面单多平台对接实战
内容概览:用快递家族生活化比喻讲清接口、抽象类底层语义,搭建基础选型判断框架,通过简化面单代码区分is-a与can-do关系,扫清概念层面所有八股误区。
- 第2篇:本文《从多平台电子面单架构看接口与抽象类的真实选型》
内容概览:基于线上电子面单生产架构落地拆解,结合DefaultBaseManager抽象基类、三层策略接口、组合式模板,给出可直接套用的工程选型标准,附带真实踩坑与面试标准答案。
📌 全文核心知识点速览(面试极速复盘)
- 抽象类核心选型场景:需共享实例状态、封装通用基础设施、统一依赖注入,适配is-a父子关系,不可被接口替代。
- 接口核心选型场景:无状态纯能力契约、业务差异解耦、多灵活扩展,适配can-do能力关系,贴合开闭原则。
- 组合模板核心优势:规避类爆炸、运行时动态适配、低耦合易测试、贴合单一职责,优于传统继承模板。
- 核心架构精髓:基础设施靠抽象类复用、业务差异靠接口解耦、流程骨架靠组合灵活编排。
- 核心避坑要点:策略路由需复合维度、通用方法加权限保护、规避接口默认方法冲突、摒弃继承耦合设计。
待更新连载
- 第 3 篇:《模板方法模式实战:为什么组合优于继承?》
内容预告:深挖模板方法两种实现方案,对比继承耦合缺陷与组合模式的扩展性,附完整可运行Demo。
- 第 4 篇:《三层策略模式拆解:彻底解耦多平台差异化逻辑》
内容预告:拆解Request/Parse/Exception三层策略设计思路,讲解策略工厂复合键路由、多平台扩展落地规范。
- 第 5 篇:《电子面单全架构复盘:从踩坑到最优设计》
内容预告:完整复盘整套多平台电子面单链路,整合抽象类、接口、策略、模板方法整套设计模式落地经验。
关注专栏,持续更新生产级Java面试实战内容,拒绝空洞八股!
你在项目中有没有纠结过接口和抽象类的选型?踩过哪些继承、策略路由的坑?欢迎评论区交流!
551

被折叠的 条评论
为什么被折叠?



