5 y" s+ q$ z b! n其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会增加一些复杂性,有时会造成很大的麻烦。2 _5 } O2 m5 E
/ k( ^' J/ B, o) |2 s$ ~ w- ]
在抽象类中不定义默认行为存在一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果它是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 ) l; w: s# \7 G$ o 6 q, k$ U# @/ u2 L1 r {另一个问题是:如果不是采用抽象类中的默认行为,就会导致同样的方法实现出现在该抽象类(或接口)的每一个派生类(或实现类)中,违反了“one rule,one place”原则,造成代码重复,同样不利于以后的维护。因此,我认为在abstract class和interface间进行选择时还是需要谨慎的,搞不好的话,是会给程序埋下一些隐患的。 5 f- M) p! h3 G- @% c( Y: I( z- S 6 S _/ l( E, Z4 _( O三、从设计理念层面看abstract class和interface! a# l. H+ I! h2 @
2 n; b0 L8 p6 z abstract class在Java语言中体现的是一种继承关系,父类和派生类之间必须存在“is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。 0 T- Z5 j' l9 c$ R4 T3 u( q! f- Z9 ?/ ~& w
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: " s2 Q& j; F/ i- z8 I3 A! K* h ' [( T P( x& x: w- i3 P 使用abstract class方式定义Door:. I) y8 C1 E* w6 Q
3 b, o. V7 e* P3 N! z
abstract class Door {# x6 T2 M) S5 }6 D
( A7 [# p6 Q T, u: _+ p! K5 \) N abstract void open();& E. e! |* p. j" N- v
) B1 ?' v* h, J# K! E( o
abstract void close();2 t( s* E* ]! V5 x4 ~
% _& S2 h% H0 K
} . I% T e$ x! R) C6 B 1 q: [& i! g0 u9 a) z, k' G 使用interface方式定义Door: . I& [" v; {* K" ]7 Z ( V8 v, \' i5 R$ o' T2 B& | interface Door {$ @2 @( A! u: E3 |# ~5 F
/ P2 |$ W8 b, n, D t void open();/ ~3 b. }( C! S
6 J1 V* q- W2 n
void close(); 2 f4 _& d* f v- b4 G/ g ( X7 x7 ?4 q1 g( A } 9 ]8 K$ H5 x H- U) H% h6 {7 T: |/ m$ o6 B) |+ @
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 ; V& ^; @& i5 s" w9 P) k : z* n2 e+ A2 C6 I6 c1 u w 如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略),下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。 1 ^, t- A% f' q4 e% w- v! D( S
解决方案一: 2 _9 w5 p: M, p- O/ m; [, w- }, A8 E8 k
简单的在Door的定义中增加一个alarm方法,如下: 0 d9 ]6 W. y2 N/ ~0 g # K4 ^+ j6 W" F2 o% @/ ?" Z+ S abstract class Door {! J2 Q' T4 R- ?, s' K, {" @5 a
4 y8 A3 b# ]" S6 \
abstract void open(); ) `( p) M3 J$ n1 p% q9 y4 b7 B/ U$ T/ O. [% X- [2 v3 r
abstract void close(); % o" }, G' G( q- O 5 ~8 ? Y9 b2 _# t& i0 E% G! W7 v abstract void alarm(); ( u0 \( b h+ {8 `2 y$ r' w9 A4 C1 i4 F
}- f% i C! Y8 p% V: {) a
: L1 c6 [ j' W& t! B( J6 i 或者( u" v+ l* t* w+ m
; I3 h% K; r5 o2 S9 e9 L, E( I4 a interface Door {+ F" Z* }# g' u% E( |' X
- q; w$ \3 o$ C% Z$ T! L; n, J
void open();* l( }; n% V# R4 O; v
+ i6 ^$ G2 A; }- X& O5 A
void close();% T+ L+ T( T! O$ `0 p
h. e! l6 K' T% { void alarm(); $ { E" L$ j1 d6 [8 c: I, P( i4 l
}- R9 f) x' @/ j6 ~/ |% L# d1 z
* ~" Z3 I1 ^% t 那么具有报警功能的AlarmDoor的定义方式如下:1 f& V. G3 j8 g' ^( v
2 P$ B$ P P/ m9 y4 j5 C8 ?; t class AlarmDoor extends Door { K1 b% e( m; f l8 G( q! h! O
& m6 }! m; Y$ A' r) f, i% c void open() { … }+ C) o1 I& L3 ~7 F$ A' [1 E& ?# Y" O
. L+ n2 w/ y4 G3 d: G- {
void close() { … } 1 {8 p' X n. \! Y/ L7 [/ I" m) j. M* H* D
void alarm() { … }0 G" q0 A" S1 O! Y8 z8 G
6 J) p& f5 k5 ^ s# V } 5 K" k& P3 I' t; U: B7 U- M$ o# Z9 i! l q# H& x6 {
或者+ M1 Z' B+ ^# t. v
* a, r( f# x4 {! o* R2 m) y7 f. T
class AlarmDoor implements Door { ; ]! N1 \# I' Q$ z% q* Y" U1 D+ D 5 p1 J4 A# D# l5 i# s* z* @( _. o void open() { … }0 u( F+ f! Q6 B) ~5 `4 I
, P' N' I- @. b& c/ E void close() { … }% q. ~7 m! g) V- t, Q, h& z
4 y/ b6 ]2 k9 d9 b- p( |7 l
void alarm() { … } 2 o+ e7 E2 d" `- ]' d* Y + }# h1 D' _) [! r! O' k4 }# |( k } * A/ _1 l9 z$ _6 D& `7 F% n ]6 Y* H4 Q" F" r X9 M5 z
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念“报警器“的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为“报警器“这个概念的改变(比如:修改alarm方法的参数)而改变。 ! c, I5 h0 O% q% @- W' J+ o* N. N2 c
解决方案二:+ T% i! P* L# y
- v; k- H% z# j$ w2 H+ R* }# ? 既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。 ; j3 C8 e0 J$ e; S - L6 c) T; [0 }" _+ c' n 显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。1 E0 l( w4 t6 _ M0 V/ C) w
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。7 n9 D1 j( N: X" X: E8 N
0 s" m9 M% J( Z, M" ^; _ 如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是“is a”关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:7 r0 u2 z& E, a, w9 V& ~
% F2 r/ B" k: n4 p
abstract class Door {( X' p- {4 X8 q/ b# ~( H
, e" r0 j4 q% i$ X7 t0 t. M abstract void open();; D: C2 Z9 v2 u
% U9 v' @, X+ ^ n& x9 o! m
abstract void close(); F, a( O; t/ l6 e' v; v0 b- d* N- s$ N9 ?; h7 r9 n
} : x! f5 _3 `- z2 A) }0 e+ I& I1 Z 8 v0 M" E5 o0 s/ z) c interface Alarm { 8 b7 F# y- [: T3 Q4 A2 K" I; ]! d9 K8 M& |5 K
void alarm();7 m8 w. S" ]: }* ~9 p
4 a7 u! g- }0 I5 p. K' a2 r9 ? }0 e* o( _% x* @, f7 f
, m4 u: s/ g6 g7 U/ X
class AlarmDoor extends Door implements Alarm {" L' v5 m, E& U. [
5 @ `( Z; ~: l void open() { … }& C, f( r9 Z# n+ Q; N6 w. u
$ c# j9 f+ y# V9 d% b
void close() { … }7 |% i8 u2 C3 Y& H4 E
+ E( |# N, d. B$ H6 P3 M. t+ z
void alarm() { … } % D6 q% B) ^3 u: T3 ?: ]0 `, D+ @3 [. [$ y
} 9 {# p: i' ] Z / K: W+ v2 O8 D6 q4 i$ O. O# B 这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是“is a”关系,interface表示的是“like a”关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。- q/ u0 D( H, c" S; D
其实在某些时候,这两个特殊的类的使用是可以互为替换的,但是在较大规模的系统工程设计时,如果不能正确运用好它的话,可能会破坏程序的结构,增强模块间的耦合度,给程序的维护与升级带来较大的不便,希望大家能够细细体会。" k/ c' u X9 B+ i' a5 ?, E
" T" w# r4 m; F8 H
& i, V3 h' P `/ J( C6 `$ G. D4 l1 S