& W7 `; i) Q- l# R. J; x/ b7 _5 M首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。 $ s& ]. D& X, i: v" i+ i, V! F9 O2 n n9 M% d/ f+ w
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会增加一些复杂性,有时会造成很大的麻烦。 / Q5 c( l7 ~7 ~- [; w2 p 0 D9 i) I9 _3 j7 O在抽象类中不定义默认行为存在一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果它是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 " F. O% N z& N. r/ {' N8 F- t- ?$ A- x& [+ l3 P( A
另一个问题是:如果不是采用抽象类中的默认行为,就会导致同样的方法实现出现在该抽象类(或接口)的每一个派生类(或实现类)中,违反了“one rule,one place”原则,造成代码重复,同样不利于以后的维护。因此,我认为在abstract class和interface间进行选择时还是需要谨慎的,搞不好的话,是会给程序埋下一些隐患的。 $ ^: N6 T { Y8 D. F/ J/ X' F3 N, S) z
三、从设计理念层面看abstract class和interface 8 ]! A0 @( B, J* i1 \9 }# o4 c
abstract class在Java语言中体现的是一种继承关系,父类和派生类之间必须存在“is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。/ ]$ h7 I+ n' ?6 f# W6 C/ j+ |$ S
+ y! [5 V+ M- Q1 @$ T Y 考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: / M4 k4 @; d* V9 I7 m5 _5 M2 s+ Y" _, t+ \' ^# m0 C
使用abstract class方式定义Door: 7 }) _3 r! u& Q& k9 o+ J: {0 A2 u( V+ l, x
abstract class Door { ) M/ ]7 C3 ^9 g7 Q8 L ^% i A" @6 J7 m; k3 L- {
abstract void open(); 4 Y" d; n/ P+ W; g1 \: n$ E, h+ O) ~! _ I4 D5 q0 ~
abstract void close(); 9 M* i% q' N F# W7 }5 x k1 u* M. s. ?3 Z3 Y
}7 H% Z* J8 { q# h, p# l
$ q# @" l% \+ f5 I% { I0 c
使用interface方式定义Door:5 |4 K Y$ N* j% p1 h8 q
3 q8 p$ D& ? R. _ interface Door {) s& _4 y, o/ X9 n( g& O
5 g# B% O% {1 V
void open(); # O: V( x5 Y- J 9 S, h( [$ V* z2 M9 ] void close(); ; R* C; B) n8 w # \1 O% t: u9 @4 T! m }! o1 Q9 ?' u" r R$ C& s/ M1 h
4 i: _" A" s1 }. S+ v8 P3 K1 i
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。, I% G# @3 v4 ^/ y" S: A# j
5 k7 v3 u3 K- q4 v9 x; c 如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略),下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。 4 r4 v/ n' j0 U N1 c 8 @* @7 h/ v" y! N 解决方案一:+ ~ x4 V9 s/ i/ Y6 o; a
: o" v- ^3 Z; m/ c4 G0 X4 \+ @
简单的在Door的定义中增加一个alarm方法,如下: ! f" G0 j( n' H0 z; e5 `7 o' @# M7 ?, o% N: M5 g
abstract class Door { # X$ i+ ~8 M9 @, m# i. g - r. J* b. b2 n/ E2 c, l/ x abstract void open(); 0 k* ]+ c/ j. a& L+ O8 @: h7 l! L# A" P9 ?: m7 L! j
abstract void close(); 0 B1 n J( }% e7 y( a3 e( m: H) k- Y) n4 p# R, P) k; O6 [
abstract void alarm(); , U4 c6 v. c. ~ 3 w7 |1 i4 u q4 d/ U% } }6 D) |6 W/ V1 {" ~' |7 O1 f
. s @" U# c+ f/ [3 x+ N
或者2 E0 u C9 }. h9 T
/ [* m8 m+ A% T$ m# Y% N" d interface Door {2 Q# o/ H- |8 `' y3 s; ?
# O2 H' ?' ^+ S' z. S
void open(); 3 r- @4 ?- w: V. I( q- a% U! N& [7 m6 @
void close(); - ^6 a1 k# t: a' Y" t( _* `/ e% C7 |3 p" w
void alarm(); 3 e Z* G$ L$ ` % a( f! w4 \4 H( _2 v2 b" F }9 q6 \9 `4 b) t* n+ B' Z; b9 U
/ ^/ E- q0 Y" B* B. @( j: u 那么具有报警功能的AlarmDoor的定义方式如下:% t* s- j' R: Z% s
; a$ @0 t2 }2 Q7 z d' S$ f2 z3 x
class AlarmDoor extends Door {. n" Z1 ?, `! J$ t
/ \ E: G P& y1 n# P
void open() { … } . t0 I5 w3 W0 Y: k 7 W, c: p9 g, K" [4 w. ]$ ^1 |5 {! a5 L void close() { … } " y3 i8 P1 j& @3 d; c3 Z1 j! B2 m- H. t3 W# }; u) a
void alarm() { … } # A/ b: i: g' O P( R6 T3 C( Y, v8 I" G9 i2 c0 a6 Z
}1 o/ V: D- a2 b) D F
/ n4 I$ G2 F1 j8 i- h1 |
或者 $ f7 M1 _# \" @0 J9 U6 ^/ H1 J; r1 w, h4 u% ?9 l6 i
class AlarmDoor implements Door {7 B! ?9 k, x( K: }) W$ |) J
; w7 j. C# r. P
void open() { … } ^- _' [' P. C6 J
) D8 d& ]/ L4 l; S" e! c void close() { … }3 I s+ _6 u6 ], R
; c L' p! M9 ~6 K" a- d, j
void alarm() { … }, _4 v9 z5 Z( P: o% `) o
. B4 N, U* b+ e
} 8 X& \, ]2 i ?# @3 e7 Y6 t1 u7 i' b% ?: @7 x' ]) D
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念“报警器“的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为“报警器“这个概念的改变(比如:修改alarm方法的参数)而改变。 6 G% J6 l0 a3 P ^4 y" a2 d9 U8 ?5 y+ v6 ~3 y. k3 D# u
解决方案二:4 _; }% J* H" f4 \
5 ]! T: k# H' V0 j0 U: r V 既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。1 S' s" G# U, N$ n) Q( ^3 M: j1 G
4 L1 s% e. u! X- N1 a4 S% l 显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。 . f% j! E& H8 H' X- m" \* d) e如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。 * E- _9 {* I$ K2 Q6 P: u* ]' c5 D
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是“is a”关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:6 J% M( k+ k2 B8 [0 B8 b3 G# s. P
! A4 g7 Z4 N2 ]1 t6 {9 r+ w
abstract class Door { 9 u# y8 l" ~; {! e % b! x; K2 I( e+ [6 ~2 k abstract void open(); ; j E; e+ ~9 J" i: _; d: a7 r; L& W+ p# |: Q8 }: s# X
abstract void close(); " F" C3 \. t# H" \3 m 7 b9 K I; k* Z, y: p% i, I4 `" q } % w4 f9 r( w; L+ w3 A: k3 I6 Z( u6 X3 B& l
interface Alarm {2 E+ n9 N/ s7 v
' i: W! C3 x) _2 ]8 K void alarm(); $ N0 y }, C% Q ^5 D% x' C$ N- I6 a5 `, Y: r! o
}0 A; p" Q& [2 Q( j w
7 J& f h( N" t( f7 j: `0 r: ` class AlarmDoor extends Door implements Alarm { : g; [* W5 Q& o3 Y3 \" g # K, t1 ^3 q& j( n void open() { … }! x6 s6 G# Y/ w% O6 @