3.3 档案还是执照:抽象类与接口选型


3.3 档案还是执照:抽象类与接口选型

本节摘要:抽象类与接口的选型看四个维度——关系语义(是不是家族 vs 能不能做)、状态需求(要不要实例字段与构造器)、继承名额(愿不愿占用唯一族谱名额)、默认实现策略(模板骨架 vs 可选补丁)。本节先列硬差异清单,再给一个决策矩阵与两个实战判定,最后复盘 JDK 集合框架"接口加抽象类加具体类"的三层设计,看官方怎么把两者搭配着用。

先把硬差异摆上桌

前两节各自讲了机制,选型前先把两者的硬差异并排摆清。这些差异不是语法细节,每一条都对应一个设计后果:

对比项 抽象类 半成品档案 接口 执业资格证
关键字 extends 继承 implements 实现
数量 一个类只能继承一个 一个类可实现多个
实例字段 可以有 家族状态 不行 只能有常量
构造器 有 供子类 super 调用 没有
方法实现 随便装 抽象具体都行 仅 default 与 static 有方法体
成员默认修饰 无隐式公开 方法隐式公开抽象 字段隐式公开常量
语义 是什么 is a 家族分支 能做什么 able to 横切能力

这张表里最"要命"的两行是实例字段数量。接口没有实例字段,意味着它装不下"状态"——凡是要携带数据的模板(比如报表基类要持有上报周期字段),接口无能为力;数量限制则意味着抽象父类占用的那个名额不可回收——如果某天这个类需要换个家族,抽象类的入场券已经用掉了,接口没有这个烦恼。反过来说,接口没有构造器、没有状态,天然解耦:实现类不必关心"证是谁发的",这正是框架爱用接口的原因。

决策矩阵:四个问题问下来

图 3-3 抽象类还是接口 四问决策矩阵

图 3-3 抽象类还是接口 四问决策矩阵

把矩阵落到两个真实判定上。判定一:给业务实体加"可排序"能力。订单、会员、商品都要排序,排序规则按类型各不相同。选抽象类意味着订单类不能再继承别的家族——荒谬;排序不需要携带额外状态、不需要流程骨架,只要一个"比大小"的约定。结论:接口,JDK 里正是 Comparable,配合比较方法 compareTo 使用(第 5 章 TreeSet 一节会实际用到)。判定二:给"报表体系"做骨架。日报、周报、月报共享上报周期字段、审批人字段,共享"收集、汇总、审核、归档"的固定流程,差异只在个别步骤。零状态装不下字段,default 补丁拼不出焊死的流程。结论:抽象类加模板方法(3.1 节 Report 案例)。

实案复盘:JDK 的三层设计

集合并不能只靠"接口或抽象类"二选一做成。JDK 的做法是三层搭积木,以列表家族为例:顶层 List 接口定契约——按下标存取、按下标遍历、按下标搜索,全世界都认这张证;中层抽象骨架类实现了接口、并把"基于迭代器的通用操作"(按内容判等、转字符串、批量删)一次性写好——任何子类只要提供迭代器,这些方法免费获得;底层具体类 ArrayList、LinkedList 各自管存储结构,顺手继承骨架类。三层各赚一份:

// 用简化代码重现三层分工(JDK 真实实现远比这复杂) interface SimpleList { // 第一层 契约 void add(String s); int size(); String get(int i); } abstract class AbstractSimpleList implements SimpleList { // 第二层 骨架 @Override public String toString() { // 通用实现:只依赖契约方法 StringBuilder b = new StringBuilder("["); for (int i = 0; i < size(); i++) { if (i > 0) b.append(", "); b.append(get(i)); } return b.append("]").toString(); } } class ArraySimpleList extends AbstractSimpleList { // 第三层 具体类 private String[] data = new String[10]; private int n = 0; public void add(String s) { data[n++] = s; } public int size() { return n; } public String get(int i) { return data[i]; } } public class LayerDemo { public static void main(String[] args) { SimpleList list = new ArraySimpleList(); // 调用方只认第一层的证 list.add("登记"); list.add("变更"); list.add("注销"); System.out.println(list); // 输出:[登记, 变更, 注销] —— 第二层的免费礼物 System.out.println(list.size()); // 输出:3 } }

ArraySimpleList 自己没写 toString,却打出了漂亮的列表格式——第二层骨架替它干了。复盘解读:如果只有接口,toString 这类通用操作每个实现类都得抄一遍;如果只有抽象类,调用方就被绑死在家族上,换实现要改代码。三层结构的分工——契约归接口、通用实现归骨架类、存储结构归具体类——是"档案与执照搭配"的标准答案,第 5 章讲集合框架时你会见到它的完整形态。

选型的最后一条忠告

初学者最常见的错误顺序是"先想类,再想要不要抽象"。更好的顺序是反过来的:先问调用方需要看到什么(契约),再问实现方有多少共性(骨架),最后才落具体类。契约先行的好处是把"使用者"与"实现者"隔开——只要契约不动,实现随便换;这也正是第 2 章多态加第 3 章契约合起来给出的设计能力。当一个抽象类只剩抽象方法、没有任何状态与骨架时,多数情况下它更应该退化成接口;当一个接口开始堆满 default 方法、彼此还有调用关系时,往往说明它其实想要一副骨架——该还俗成抽象类了。

本节要点回顾

  • 硬差异三条最要紧:接口无实例字段无构造器、抽象类占唯一族谱名额、接口可多实现——分别对应状态、演化、扩展三个设计后果
  • 四问决策:语义是家族还是能力、要不要状态、名额怎么花、默认实现是骨架还是补丁
  • 两个实战判定:可排序这类零状态横切能力归接口(Comparable 是范本);带字段带流程的体系统一归抽象类加模板方法
  • 正解常是搭配:契约归接口、通用实现归抽象骨架类、存储与差异归具体类——JDK 集合三层设计是标准答案
  • 设计顺序契约先行:先定调用方看到什么,再抽实现方共性,最后落具体类

机制部分到此收官。从下一章起进入工具房:Java 已经登记好的海量住户——包装类、字符串、集合、流、时间——等着我们去查它们的档案。第一站是拿着暂住证进城的八个基本类型。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U