抽象类是带骨架的共性容器,既封装可复用的具体逻辑(如校验、日志),又定义子类必须实现的抽象行为;命名需体现契约,避免伪共性,用钩子方法和final主流程平衡复用与定制。

抽象类不是“空模板”,而是带骨架的共性容器
abstract 类的核心价值不在“不能 new”,而在它能同时容纳已实现的共性代码(比如通用日志、校验逻辑、构造初始化)和待子类填充的抽象行为(比如 calculateArea()、makeSound())。它比接口更重,比普通父类更松——既提供可复用的“肉”,又保留必须定制的“骨”。
把能写的先写死,把不确定的标成 abstract
提取共性时,别一上来就全 abstract。先观察子类重复出现的代码块:字段声明、getter/setter、工具方法、构造逻辑、甚至部分业务流程(如“下单前校验→扣库存→发消息”中前两步固定,第三步因渠道而异)。这些都该放进 abstract 类里;只有真正因子类而异的部分,才定义为 abstract 方法。
-
protected String id;和public final void validate() { ... }可直接写在 abstract 类中 -
public abstract BigDecimal computePrice();留给子类各自实现 - 子类构造器里调用
super(name, id),复用父类构造逻辑,而非各自 new 字段
abstract 方法命名要暴露契约,别藏逻辑细节
抽象方法名不是随便起的,它本质是子类必须签的“行为契约”。比如用 render() 而不是 doSomething(),用 serializeToXml() 而不是 output()。参数和返回类型也要足够明确——如果子类实现时总要额外传 context 或 config,说明这个方法签名本身就没抽好,应该把共用依赖提前放到 abstract 类的字段或 protected 方法里。
常见错误:public abstract void handle(Request req); 看似合理,但实际各子类都在里面做 if (req.getType() == X) {...} 分支判断——这说明“类型分发”本该由 abstract 类统一做,handle() 应拆成多个更细粒度的 abstract 方法,或改用模板方法模式。
小心 abstract 类里的“伪共性”陷阱
最容易被忽略的是:abstract 类一旦加了具体方法,就承担了隐式契约责任。比如你写了 public void save() { db.insert(this); notifyEvent(); },那所有子类都默认继承这套流程。之后若某个子类想跳过 notifyEvent(),就必须重写整个 save(),反而破坏复用。
更稳妥的做法:
- 把流程拆成钩子方法:
beforeSave()、afterSave()(默认空实现),主流程save()调用它们 - 用
final修饰主流程方法,防子类覆盖逻辑,只开放钩子 - 避免在 abstract 类里写带副作用的具体方法(如发 HTTP、写磁盘),除非你确定所有子类都需且仅需这一种方式
抽象类的复杂点从来不在语法,而在“哪些该放进去、哪些该抠出来”——它是一条不断调整边界的线,而不是一个静态的分类筐。

















