2 P. g* A: k& V9 Q2 k# w首先来理解一下抽象类。在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如我们xpads 项目中的交易类就可以理解为一个抽象类,即期交易、远期交易、掉期交易等都是继承交易类的具体类。即期交易、远期交易、掉期交易这些具体概念是实际存在的,而交易这个概念在问题领域中是不存在的,可以理解为一个抽象概念。正是因为抽象概念在实际问题领域中没有东西与它对应,所以用以表征抽象概念的抽象类是不能被实例化的。 6 X* I% `5 { S6 l! \3 x2 {2 R+ Z ! o. Z+ j% n7 Y接下来理解一下接口,接口是面向对象设计中的一组规范,是一组方法的集合体(也可以定义一些常量),在一些软件设计模式中提倡的面向接口编程,其实就是屏蔽实现类的内部处理,增强模块的安全性、灵活性,达到软件设计的高内聚、松耦合。 % z4 B# V- u, a+ g+ `* [( A# {0 ]. l& t4 s9 W- A+ \% b/ P
下面从三个方面进行比较:3 J, o @8 x7 \
' G3 `0 v( l- [6 I一、从语法定义层面看abstract class和interface 4 i n; f/ d1 r4 Y$ y
' T K/ d0 }# A0 V |% j使用abstract class的方式定义Deal抽象类的方式如下: 6 |6 ~% _' y N4 f o* r0 @1 S) u* p' c1 |( j9 W) x; S* U
abstract class FxDeal {7 {7 z; d2 g5 u
9 K: R) ^5 s, g# c/ A- M9 n0 r3 x3 D private long dealsId; //交易流水号 $ d( e7 s( H+ Z; L3 o1 x: } ' |% ^; e( b$ J* a* j) N, B" [private long blockNumber;//套流水号0 _4 Z& V' v# I0 r6 Q' V0 r6 U# g
. @3 ]* ~6 T- r1 T
private String appls;//产品类别 5 c9 b4 y- H- E/ c 8 ^: Z8 o- T9 W, K$ nprivate String inputChannel;//录入渠道 # a+ t5 |! o. y$ H! j- l* P2 \ 6 D. d; f' w. P% ?' [private String typeOfDeal;//交易类型 4 x" q4 `* C4 C, d" Z2 A6 s7 p) ~3 @+ `7 ]7 H$ ~+ ~5 U
private String flagOfDeal;// 特殊交易标识& v% s, B, ^ w4 q6 M( S' k, B
: p2 `2 e/ B2 y, t& gprivate String bankId;//业务发生行/ P2 d z' ^+ s$ A' r( \% u; c \
, \ P% h: a0 N$ v( [8 X7 n: M" }private String customerId; //客户号$ b# u- Z/ s0 k
) ]$ B* A, ]0 u
private String customerName;//客户名称 5 \' O$ `1 L5 \4 M4 d) C2 c% H8 L* t+ A3 b) ^7 J, U, O
private int customerType;//客户类型* d; |0 B2 `/ _
( G; i8 y+ m0 t8 ^6 i0 l# i# o
private double amount1; $ P8 |' ^8 G/ m3 a& q. H$ ]+ U7 [3 o
private double amount2;2 Q% H g$ K8 I0 E
6 Z) T' E t) ]' Z! J4 B8 G' T! G abstract void method1(); ! j9 ~9 @2 E: p* f: U1 m+ M, z8 g3 M; _- o1 A
abstract void method2(); % w( }6 p9 k h G: S$ m 0 s$ b7 u, s) P/ F9 P( D … 6 U4 _% Y$ P1 q; Z/ l, O F( Q3 [
}. y) }% ~: M" p: Y/ p
+ `7 }* A7 n% O- C; R在抽象类中不定义默认行为存在一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果它是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 ! p) P2 ~0 x8 K8 ?5 w 7 E' s8 q' T3 h# Y4 G& V. ^另一个问题是:如果不是采用抽象类中的默认行为,就会导致同样的方法实现出现在该抽象类(或接口)的每一个派生类(或实现类)中,违反了“one rule,one place”原则,造成代码重复,同样不利于以后的维护。因此,我认为在abstract class和interface间进行选择时还是需要谨慎的,搞不好的话,是会给程序埋下一些隐患的。8 o+ Z, `5 G" F7 M
7 y4 z: \! ?' f$ g0 y# Z
三、从设计理念层面看abstract class和interface1 j2 k3 Y$ x5 @
0 D- O5 `3 D1 n9 y
abstract class在Java语言中体现的是一种继承关系,父类和派生类之间必须存在“is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。- r1 I. k& x& I$ {4 H
$ {9 u9 S# L) t& U9 ]- K& B/ t 考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: , Z: h2 D y) B/ @ D * g ^1 Y1 _) b9 y 使用abstract class方式定义Door:, E5 B' I d; t2 J, U
6 H: X; e2 c. @1 {) Y. o( T9 O abstract class Door {! ^4 V' B5 B5 ~) d
0 }/ y! t* X1 w; h! b6 C7 }3 h+ M& A
abstract void open(); 0 Z! \$ k+ X! V; O+ j$ y, x - r* T: y. w* s' y2 | abstract void close(); + Z- e6 X o. B3 J6 ]) ~6 m! W1 ?; k
}2 C; C5 w' @3 ~; |
% V, x6 {; g) F+ `4 V1 j, D, c
使用interface方式定义Door: & y) L i; s: `% i0 ]0 i, U* H. B. d/ d
interface Door {6 Q5 j' b9 [* d4 n" T' w$ j F9 X
4 X: S! X5 {$ _/ | `+ X8 |- p
void open(); ; ?5 p$ R- M2 @! e 7 s. v* J3 g9 s; d void close();. `* u! Q! o8 n( [# N+ t( s5 K' E
) w, a/ u1 C( D } % c9 \+ f3 Q& k( Y: A1 }+ D) R' ?" r
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 6 G1 e& {# F: a7 I$ r* Y' H ) I. w. ]9 }" p: E 如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略),下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。) W/ H! T0 p# N
8 J; x4 t& H9 j1 w! a
解决方案一: ^/ ]# @: J# s6 L . D. {3 ~/ b0 L4 G% N 简单的在Door的定义中增加一个alarm方法,如下: 1 Q" q0 p4 i' h3 J+ u0 b' r5 D+ {* g, b
abstract class Door {& k& V8 [* I4 Q& k0 U% B
/ n" `. ?0 n+ l& L abstract void open(); 6 k& Z+ ^, T1 w8 Y; {# O0 q # G8 J% `9 ], s9 C- B' c abstract void close(); , a9 K0 d# u( u. l3 X 2 U9 s4 V/ Z5 F2 c abstract void alarm();& G0 S. S$ {( O# W) q* ^. ?
0 W% Z0 l4 A1 O/ o6 \3 ~
}% B! K. H% q$ D4 ]! h# Q& g( E/ E
7 l4 i+ K( Z( _0 d, J5 B 或者 1 S: d' |+ z- O5 f: H' x5 b3 H 3 {% Z" e/ y2 I/ b interface Door {2 ^$ u, L+ K0 x- ~
5 O( W0 O3 C4 d _1 y' O' D
void open(); 3 x5 V& w6 W# U7 _& ?/ ?1 E; O2 s7 e' d8 A/ ~& |2 T
void close(); , A& r6 r: I- o2 y0 j3 s2 @: U6 F$ h
void alarm();6 H2 P) l k) o. E+ A
1 n7 D( Z+ ]3 n* w, L
} $ W W# {4 E: W* w% o$ Z1 W5 W# I, a! U
那么具有报警功能的AlarmDoor的定义方式如下:2 q# Q. x2 X6 T, G
3 h0 u( B$ |) F' P class AlarmDoor extends Door { ) F" ?5 x0 X9 t& Q, l u B7 h, |& r r! m& `! }) t void open() { … }3 ]% ]) H7 G. g
2 J' n; D# G, O6 s/ K M
void close() { … } 1 L0 ~) _( a. F- P. X6 ?# F1 ^0 V8 V; H; m- s3 h; ]& ?
void alarm() { … }0 M$ _: s0 \. k, r
8 X |( Y$ G2 x! u* V, [* |
} . a1 v$ ?6 U% V! L7 I3 c% W& ^( X) B0 \0 [
或者 ' R: Y: t5 T# s& J/ _$ ^; ]1 t 7 q0 Z) e# |' B class AlarmDoor implements Door {7 B6 g: [7 t+ S9 ]& R! W
4 D5 d2 Q0 @# v' I$ [
void open() { … }# h5 k/ ]& |6 i# s7 U' _; |
+ x( n/ Y4 ]! r7 p1 P
void close() { … }3 m9 H( Y) c C& i/ @7 o* D+ m: ^/ ^
; ]. u0 f1 c/ e( c+ T void alarm() { … } ! V6 F% l8 v$ ^( m% h2 R5 ~6 w! m 6 ^2 M! l3 w6 V' b }7 {! F& A' u3 i2 e. _ v1 f
. f1 E/ {# n( h( J
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念“报警器“的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为“报警器“这个概念的改变(比如:修改alarm方法的参数)而改变。 # [6 J, N) B) v5 y$ r6 H; o2 o, E1 D9 J9 T# s& z1 f9 X0 o- j
解决方案二:! P5 e, R3 w F9 m& Q' h7 l. A
" T- E- |' s' z) ^ 既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。7 ?% v4 Q4 `2 I5 q/ N
$ l; J1 ^; G1 ]: `! i
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。 & a6 I# g4 |# f0 z6 `1 A* D如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。 ! i2 n* }! m6 Z/ y. W4 N/ Y# d; F" h% a; F1 O" N
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是“is a”关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示: - V/ q9 a$ F* e6 f6 m $ U, v! u4 D. }: x% m0 ] abstract class Door { . r6 v6 M6 ?# v g" R$ |2 P( @& q$ ~
abstract void open();6 N/ N5 i0 i! k- M" j" T
+ M' p8 {9 D0 j
abstract void close(); 5 T* v" }3 Z, L5 Y! b$ a0 h/ d- [% V1 h$ c0 I0 S
}8 h' Z4 E) I( A- S. R- b
3 R u2 V1 X; C t( K" e8 M$ v interface Alarm { ) K4 B1 x, i: ?0 h* Q( a$ G$ m , e6 p; a" c3 S/ t: w! G; u( D# w void alarm(); ) I8 [) `) w9 p- h' H! a) ]. @7 P 8 K( j! |' H! |, `% C% s } , k: F. N: z( p0 p9 F: @; s' v6 Q6 q . w) @/ H0 Q4 R5 {( I/ |: u2 [# B! I: G class AlarmDoor extends Door implements Alarm { 5 G5 T6 r6 _' T: Q4 r- m7 A/ T 5 }) o$ M/ d8 p* f void open() { … } ' Y" [1 r3 M' b. j" s/ G 6 ?$ c2 {6 g" \' c) g void close() { … } % g6 x' _1 c( ~ ; f0 c6 \# m! o3 D& L" P void alarm() { … } 1 U2 J6 ~% K! i9 Q; w3 L6 k 0 F# \4 V" J9 } } + ]2 C8 r; c6 W" r: \& q- ] ( ^5 ^. V I$ R3 y: h/ T" g. r 这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是“is a”关系,interface表示的是“like a”关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。 2 @: j5 L& V2 a0 ?8 }0 R其实在某些时候,这两个特殊的类的使用是可以互为替换的,但是在较大规模的系统工程设计时,如果不能正确运用好它的话,可能会破坏程序的结构,增强模块间的耦合度,给程序的维护与升级带来较大的不便,希望大家能够细细体会。* C& \: p4 {( j- o7 B