abstract class
和
interface
是
Java
语
言中
对
于抽象
类
定
义进
行支持的两种机制,正是由于
这
两种机制的存在,才
赋
予了
Java
强
大的面向
对
象能力。
abstract class
和
interface
之
间
在
对
于抽象
类
定
义
的支持方面具有很大的相似性,甚至可以相互替
换
,因此很多开
发
者在
进
行抽象
类
定
义时对
于
abstract class
和
interface
的
选择显
得比
较
随意。
其
实
,两者之
间还
是有很
大的区
别
的,
对
于它
们
的
选择
甚至反映出
对
于
问题领
域本
质
的理解、
对
于
设计
意
图
的理解是否正确、合理。本文将
对
它
们
之
间
的区
别进
行一番剖析,
试图给
开
发
者提供一个在二者之
间进
行
选择
的依据。
理解抽象 类
理解抽象 类
abstract class
和
interface
在
Java
语
言中都是用来
进
行抽象
类
(本文中的抽象
类
并非从
abstract class
翻
译
而来,它表示的是一个抽象体,而
abstract class
为
Java
语
言中用于定
义
抽象
类
的一种方法,
请读
者注意区分)定
义
的,那么什么是抽象
类
,使用抽象
类
能
为
我
们带
来什么好
处
呢?
在面向
对
象的概念中,我
们
知道所有的
对
象都是通
过类
来描
绘
的,但是反
过
来却不是
这样
。并不是所有的
类
都是用来描
绘对
象的,如果一个
类
中没有包含足
够
的信息来描
绘
一个具体的
对
象,
这样
的
类
就是抽象
类
。抽象
类
往往用来表征我
们
在
对问题领
域
进
行分析、
设计
中得出的抽象概念,是
对
一系列看上去不同,但是本
质
上相同的具体概念的抽象。比如:如果我
们进
行一个
图
形
编辑软
件的开
发
,就会
发现问题领
域存在着
圆
、三角形
这样
一些具体概念,它
们
是不同的,但是它
们
又都属于形状
这样
一个概念,形状
这
个概念在
问题领
域是不存在的,它就是一个抽象概念。正是因
为
抽象的
概念在
问题领
域没有
对应
的具体概念,所以用以表征抽象概念的抽象
类
是不能
够实
例化的。
在面向
对
象
领
域,抽象
类
主要用来
进
行
类
型
隐
藏。我
们
可以构造出一个固定的一
组
行
为
的抽象描述,但是
这组
行
为
却能
够
有任意个可能的具体
实现
方式。
这
个抽象描述就是抽象
类
,而
这
一
组
任意个可能的具体
实现则
表
现为
所有可能的派生
类
。模
块
可以操作一个抽象体。由于模
块
依
赖
于一个固定的抽象体,因此它可以是不允
许
修改的;同
时
,通
过
从
这
个抽象体派生,也可
扩
展此模
块
的行
为
功能。熟悉
OCP
的
读
者一定知道,
为
了能
够实现
面向
对
象
设计
的一个最核心的原
则
OCP(Open-Closed Principle)
,抽象
类
是其中的关
键
所在。
从
语
法定
义层
面看
abstract class
和
interface
在
语
法
层
面,
Java
语
言
对
于
abstract class
和
interface
给
出了不同的定
义
方式,下面以定
义
一个名
为
Demo
的抽象
类为
例来
说
明
这
种不同。
使用
abstract class
的方式定
义
Demo
抽象
类
的方式如下:
abstract class Demo
{
abstract void method1();
abstract void method2();
…
}
abstract void method1();
abstract void method2();
…
}
使用
interface
的方式定
义
Demo
抽象
类
的方式如下:
interface Demo {
void method1();
void method2();
…
}
void method1();
void method2();
…
}
在
abstract class
方式中,
Demo
可以有自己的数据成
员
,也可以有非
abstarct
的成
员
方法,而在
interface
方式的
实现
中,
Demo
只能
够
有静
态
的不能被修改的数据成
员
(也就是必
须
是
static final
的,不
过
在
interface
中一般不定
义
数据成
员
),所有的成
员
方法都是
abstract
的。从某种意
义
上
说
,
interface
是一种特殊形式的
abstract class
。
对
于
abstract class
和
interface
在
语
法定
义层
面更多的
细节问题
,不是本文的重点,不再
赘
述,
读
者可以参
阅
参考文献〔
1
〕
获
得更多的相关内容。
从
编
程
层
面看
abstract class
和
interface
从
编
程的角度来看,
abstract class
和
interface
都可以用来
实现
"design by contract"
的思想。
但是在具体的使用上面
还
是有一些区
别
的。
首先,
abstract class
在
Java
语
言中表示的是一种
继
承关系,一个
类
只能使用一次
继
承关系。但是,一个
类
却可以
实现
多个
interface
。也
许
,
这
是
Java
语
言的
设计
者在考
虑
Java
对
于多重
继
承的支持方面的一种折中考
虑
吧。
其次,在
abstract class
的定
义
中,我
们
可以
赋
予方法的默
认
行
为
。但是在
interface
的定
义
中,方法却不能
拥
有默
认
行
为
,
为
了
绕过这
个限制,必
须
使用委托,但是
这
会
增加一些复
杂
性,有
时
会造成很大的麻
烦
。
在抽象
类
中不能定
义
默
认
行
为还
存在另一个比
较严
重的
问题
,那就是可能会造成
维护
上的麻
烦
。因
为
如果后来想修改
类
的界面(一般通
过
abstract class
或者
interface
来表示)以适
应
新的情况(比如,添加新的方法或者
给
已用的方法中添加新的参数)
时
,就会非常的麻
烦
,可能要花
费
很多的
时间
(
对
于派生
类
很多的情况,尤
为
如此)。但是如果界面是通
过
abstract class
来
实现
的,那么可能就只需要修改定
义
在
abstract class
中的默
认
行
为
就可以了。
同
样
,如果不能在抽象
类
中定
义
默
认
行
为
,就会
导
致同
样
的方
法
实现
出
现
在
该
抽象
类
的每一个派生
类
中,
违
反了
"one rule
,
one place"
原
则
,造成代
码
重复,同
样
不利于以后的
维护
。因此,在
abstract class
和
interface
间进
行
选择时
要非常的小心。
从
设计
理念
层
面看
abstract class
和
interface
上面主要从
语
法定
义
和
编
程的角度
论
述了
abstract class
和
interface
的区
别
,
这
些
层
面的区
别
是比
较
低
层
次的、非本
质
的。本小
节
将从另一个
层
面:
abstract class
和
interface
所反映出的
设计
理念,来分析一下
二者的区
别
。作者
认为
,从
这
个
层
面
进
行分析才能理解二者概念的本
质
所在。
前面已
经
提到
过
,
abstarct class
在
Java
语
言中体
现
了一种
继
承关系,要想使得
继
承关系合理,父
类
和派生
类
之
间
必
须
存在
"is a"
关系,即父
类
和派生
类
在概念本
质
上
应该
是相同的(参考文献〔
3
〕中有关于
"is a"
关系的大篇幅深入的
论
述,有
兴
趣的
读
者可以参考)。
对
于
interface
来
说则
不然,并不要求
interface
的
实现
者和
interface
定
义
在概念本
质
上是一致的,
仅仅
是
实现
了
interface
定
义
的契
约
而已。
为
了使
论
述便
于理解,下面将通
过
一个
简单
的
实
例
进
行
说
明。
考
虑这样
一个例子,假
设
在我
们
的
问题领
域中有一个关于
Door
的抽象概念,
该
Door
具有
执
行两个
动
作
open
和
close
,此
时
我
们
可以通
过
abstract class
或者
interface
来定
义
一个表示
该
抽象概念的
类
型,定
义
方式分
别
如下所示:
使用
abstract class
方式定
义
Door
:
abstract class Door {
abstract void open();
abstract void close() ;
}
abstract void open();
abstract void close() ;
}
使用
interface
方式定
义
Door
:
interface Door {
void open();
void close();
}
void open();
void close();
}
其他具体的
Door
类
型可以
extends
使用
abstract class
方式定
义
的
Door
或者
implements
使用
interface
方式定
义
的
Door
。看起来好像使用
abstract class
和
interface
没有大的区
别
。
如果
现
在要求
Door
还
要具有
报
警的功能。我
们该
如何
设计针对该
例子的
类结
构呢(在本例中,主要是
为
了展示
abstract class
和
interface
反映在
设计
理念上的区
别
,其他方面无关的
问题
都做了
简
化或者忽略)?下面将
罗
列出可能的解决方案,并从
设计
理念
层
面
对这
些不同的方案
进
行分析。
解决方案一:
简单
的在
Door
的定
义
中增加一个
alarm
方法,如下:
abstract class Door {
abstract void open();
abstract void close() ;
abstract void alarm();
}
abstract void open();
abstract void close() ;
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
void open();
void close();
void alarm();
}
那么具有
报
警功能的
AlarmDoor
的定
义
方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door
{
void open() { … }
void close() { … }
void alarm() { … }
}
void open() { … }
void close() { … }
void alarm() { … }
}
这
种方法
违
反了面向
对
象
设计
中的一个核心原
则
ISP
(
Interface Segregation Principle
),在
Door
的定
义
中把
Door
概念本身固有的行
为
方法和另外一个概念
"
报
警器
"
的行
为
方法混在了一起。
这样
引起的一个
问题
是那些
仅仅
依
赖
于
Door
这
个概念的模
块
会因
为
"
报
警器
"
这
个概念的改
变
(比如:修改
alarm
方法的参数)而改
变
,反之依然。
解决方案二:
既然
open
、
close
和
alarm
属于两个不同的概念,根据
ISP
原
则应该
把它
们
分
别
定
义
在代表
这
两个概念的抽象
类
中。定
义
方式有:
这
两个概念都使用
abstract class
方式定
义
;两个概念都使用
interface
方式定
义
;一个概念使用
abstract class
方式定
义
,另一个概念使用
interface
方式定
义
。
显
然,由于
Java
语
言不支持多重
继
承,所以两个概念都使用
abstract class
方式定
义
是不可行的。
后面两种方式都是可行的,但是
对
于它
们
的
选择
却反映出
对
于
问题领
域中的概念本
质
的理解、
对
于
设计
意
图
的反映是否正确、合理。我
们
一一来分析、
说
明。
如果两个概念都使用
interface
方式来定
义
,那么就反映出两个
问题
:
1
、我
们
可能没有理解清楚
问题领
域,
AlarmDoor
在概念本
质
上到底是
Door
还
是
报
警器?
2
、如果我
们对
于
问题领
域的理解没有
问题
,比如:我
们
通
过对
于
问题领
域的分析
发现
AlarmDoor
在概念本
质
上和
Door
是一致的,那么我
们
在
实现时
就没有能
够
正确的揭示我
们
的
设计
意
图
,因
为
在
这
两个概念的定
义
上(均使用
interface
方式定
义
)反映不出上述含
义
。
如果我
们对
于
问题领
域的理解是:
AlarmDoor
在概念本
质
上是
Door
,同
时
它有具有
报
警的功能。我
们该
如何来
设计
、
实现
来明确的反映出我
们
的意思呢?前面已
经说过
,
abstract class
在
Java
语
言中表示一种
继
承关系,而
继
承关系在本
质
上是
"is a"
关系。所以
对
于
Door
这
个概念,我
们应该
使用
abstarct class
方式来定
义
。另外,
AlarmDoor
又具有
报
警功能,
说
明它又能
够
完成
报
警概念中定
义
的行
为
,所以
报
警概念可以通
过
interface
方式定
义
。
如下所示:
abstract class Door {
abstract void open();
abstract void close() ;
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
abstract void open();
abstract void close() ;
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
这
种
实现
方式基本上能
够
明确的反映出我
们对
于
问题领
域的理解,正确的揭示我
们
的
设计
意
图
。其
实
abstract class
表示的是
"is a"
关系,
interface
表示的是
"like a"
关系,大家在
选择时
可以作
为
一个依据,当然
这
是建立在
对问题领
域的理解上的,比如:如果我
们认为
AlarmDoor
在概念本
质
上
是
报
警器,同
时
又具有
Door
的功能,那么上述的定
义
方式就要反
过
来了。
结论
abstract class
和
interface
是
Java
语
言中的两种定
义
抽象
类
的方式,它
们
之
间
有很大的相似性。但是
对
于它
们
的
选择
却又往往反映出
对
于
问题领
域中的概念本
质
的理解、
对
于
设计
意
图
的反映是否正确、合理,因
为
它
们
表
现
了概念
间
的不同的关系(
虽
然都能
够实现
需求的功能)。
这
其
实
也是
语
言的一种的
惯
用法,希望
读
者朋友能
够细细
体会
。