深入理解abstract class与interface
abstract class 和 interface 是 Java 里定义抽象类型的两种机制,也因此赋予了语言强大的面向对象能力。二者很像,有时还能互换,所以不少人选型很随意。
其实差别不小:选哪一个,往往反映出你对问题领域本质、对设计意图的理解是否站得住。下面按语法、编程和设计理念三层来拆。
一、什么是抽象类
这里的“抽象类”不只等于关键字 abstract class,而是指一个抽象体:类里没有足够信息描绘具体对象。它表征分析设计里得到的抽象概念——圆、三角形不同,但都属于“形状”;形状在问题领域里并不作为实物存在,所以不能实例化。
面向对象里,抽象类型主要用来做类型隐藏:固定一组行为的抽象描述,具体实现可以有任意多个派生类。模块依赖这个稳定抽象,从而可对修改关闭、对扩展开放(OCP)。
二、语法层面
用 abstract class 定义 Demo:
用 interface 定义:
abstract class 可以有自己的数据成员和非抽象方法;interface 在当时的 Java 里只能有 static final 数据(一般也不定义),方法都是抽象的。从某种意义上说,interface 是一种特殊的 abstract class。(Java 8 起接口可以有 default / static 方法,本文仍按原文的设计理念来选:is-a 用抽象类,like-a 用接口。)
| 对比 | abstract class | interface |
|---|---|---|
| 继承次数 | 类只能 extends 一次 | 可以实现多个 interface |
| 默认行为 | 可以给方法写默认实现 | 当时不能;后来才有 default |
| 数据成员 | 可以有实例字段 | 常量 static final |
| 设计含义 | is a | like a(实现契约) |
三、编程层面
两者都能表达 design by contract,但用法不同。继承关系只能用一次,接口可以实施多份,这是语言对多重继承的折中。
抽象类能给方法默认行为;接口当时不行,绕过去往往要委托,复杂度上升。不能写默认行为还有维护问题:日后给界面加方法或加参数,派生类一多就很痛;若界面是 abstract class,有时只改父类默认实现即可。同样的实现若在每个派生类里复制一份,也违反 “one rule, one place”。
四、设计理念:Door 与报警
abstract class 体现继承,合理的继承要求父类与派生类存在 is a:概念本质上相同。interface 不要求实现者和接口在概念上是同一类事物,只要履行契约。
假设领域里有 Door,能 open / close:
看起来差不多。若还要求报警呢?
方案一:直接把 alarm 塞进 Door
这违反 ISP(接口隔离):把 Door 固有行为和“报警器”混在一起。只依赖开门关门的模块,会因为报警概念的改动而跟着改。
方案二:两个概念拆开
open/close 与 alarm 属于不同概念,应分到两个抽象里。两个都用 abstract class 不可行——Java 不能多重继承。两个都用 interface,又说不清 AlarmDoor 本质上到底是门还是报警器,也反映不出设计意图。
若理解是:AlarmDoor 本质上是 Door,同时又有报警能力,则 Door 用抽象类(is a),报警用接口(like a):
这基本能反映对领域的理解和设计意图。若你认为 AlarmDoor 本质上是报警器、顺便能当门用,定义就要反过来。
一句话总结:都能定义抽象类型,但 abstract class 说的是 is-a,interface 说的是 like-a;选型先问清概念本质,再写代码。
转载请注明来源:深入理解abstract class与interface







