" }$ P+ F: H d+ ~: a3 {: @ Z/ k 为什么你应该避免实现继承呢?第一个问题是明确的使用具体类名将你固定到特定的实现,给底层的改变增加了不必要的困难。 ! n) Y) U2 f6 t* y4 t4 r8 }- j3 \+ i. E1 j
在当前的敏捷编程方法中,核心是并行的设计和开发的概念。在你详细设计程序前,你开始编程。这个技术不同于传统方法的形式----传统的方式是设计应该在编码开始前完成----但是许多成功的项目已经证明你能够更快速的开发高质量代码,相对于传统的按部就班的方法。但是在并行开发的核心是主张灵活性。你不得不以某一种方式写你的代码以至于最新发现的需求能够尽可能没有痛苦的合并到已有的代码中。 : P( K# R; l$ {% E
! ^; u; R% P2 v* p6 v/ k c
胜于实现你也许需要的特征,你只需实现你明确需要的特征,而且适度的对变化的包容。如果你没有这种灵活,并行的开发,那简直不可能。 ) c2 u. T" ]0 J0 o" C ~' E8 g3 O% D5 I8 }. c 对于Inteface的编程是灵活结构的核心。为了说明为什么,让我们看一下当使用它们的时候,会发生什么。考虑下面的代码: * F2 i( t5 I4 J; B8 I( d
2 d J* U$ a/ N: J$ ~9 E
<CCID_NOBR> - E* |6 F8 v' y% O Z$ d<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> & D& D, S& J+ W1 {# D3 F# x, f0 N% K' }4 J4 x0 p- ?5 x
<TR> ) `) l: d# n, V* p, i1 ]4 T1 ?7 F<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>f() / L6 X- ]8 p7 k+ {3 u; I{ LinkedList list = new LinkedList();' E. Q/ {# R& A- j5 J
//...5 Y% W+ T+ [- }3 ?) b
g( list );2 k4 f$ r8 Z( a, G; E, ~( v
}) f# N% h- t7 }7 O
r5 Z1 k3 m3 N3 Z5 I& E: [g( LinkedList list ) + ]. P% j* m/ Y{ & h; N# ~7 Y. J- ~ list.add( ... ); i1 p" W0 v: b& ^ g2( list ) 2 [0 r3 H' T9 G4 r( u4 s4 o}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>6 H! s) A6 y2 E8 o( W
n- n! J% j7 p: w+ V6 k
现在,假设一个对于快速查询的需求被提出,以至于这个LinkedList不能够解决。你需要用HashSet来代替它。在已有代码中,变化不能够局部化,因为你不仅仅需要修改f()也需要修改g()(它带有LinkedList参数),并且还有g()把列表传递给的任何代码。象下面这样重写代码: 4 U' F8 }- r7 a { % ^3 k7 O! V: P* I% C1 \5 S<CCID_NOBR>) m, \+ u% p4 e( n, s
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> i1 ^! D* i/ j6 R% u$ o8 e
) z) r3 t9 |# }) L/ } R% Y
<TR>2 t9 a4 n: ^- ?& {' [" D
<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>f() 7 Z! P. A' K, v/ u/ H* z{ Collection list = new LinkedList();& E, r' z) E' g) |; x4 ]- b
//... 7 B# M3 J. I7 a" S g( list ); , b9 S0 a0 Y! P- p5 _}( K$ O# z Z$ ~3 ]
1 W/ H/ H& H* u" _! Wg( Collection list )% F. G/ Q! K/ d9 v# u1 V
{ - [7 O* ^( u( r, L) |1 z list.add( ... ); ) ]* A5 N) l) Z* v% E, c g2( list ) 6 N6 U- g3 S, N) c, N}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR> + b7 _, H+ `$ o( w ; Y5 p4 K7 e$ n* S( h9 Z, `" c 这样修改Linked list成hash,可能只是简单的用new HashSet()代替new LinkedList()。就这样。没有其他的需要修改的地方。 $ [8 X9 k' c2 e8 `7 [, C . v1 N2 k, W5 @# M: P. d" \ 作为另一个例子,比较下面两段代码: : s( V1 ^% I) I2 X4 G4 p' p
+ N& ]2 Z# C) [<CCID_NOBR>% r8 m# R+ ^! s& O/ {* M( V
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540># |/ _( \0 L* l5 m
, I: K& L4 |! j( l! Q( U. A<TR> 2 {5 W- V- W4 V<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>f()4 v2 z' Q1 y: S0 ]% W, @2 a7 v! p5 ^
{ Collection c = new HashSet();$ x; _# r7 Q7 Q' S8 G( R
//..." n$ c, o7 _4 X. t$ N+ ]2 P
g( c );9 Q R8 \- H3 u# q( u. ~, c e
}! q6 x# V. ~% x( I
3 P1 C4 j( t' s! m, h6 a2 P i
g( Collection c )1 j# c/ L* c s
{/ B6 n) X/ A" l2 o
for( Iterator i = c.iterator(); i.hasNext() )0 O7 U, k3 [( h6 `9 X' C, T
do_something_with( i.next() );8 _8 A9 P( F- z. o# S# c
}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>) L8 y1 B( {6 ]4 j
, e5 i9 s# ^* {3 O- p5 ~. F和 ) ]$ z2 V: Z4 c5 [+ s6 p' s1 N
& ^' H* l# a. s4 _8 \: L9 k/ F7 \9 r
<CCID_NOBR>: p0 h' i, U$ q4 {; O7 E4 n8 p
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> \; w6 o8 I0 H: J% J8 [6 ^1 l
, A6 h* S9 Q0 i5 t<TR>, T6 M: W2 Q0 x7 n/ B1 S
<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>f2() 2 K0 U8 `: @% i{ Collection c = new HashSet();' k, q( z( F9 N* \
//... _* `5 i$ w" f2 B9 r B g2( c.iterator() ); & E7 t5 L4 c' R- \ V4 m; I} + A. Q k5 p4 K1 P3 Q9 Q 1 G! l9 E, r! E6 rg2( Iterator i ) + ^1 n3 Z' [ Z{ while( i.hasNext() ) . p- R/ a5 G7 H) E, R$ Z3 U; X* H8 N: o+ z7 i do_something_with( i.next() ); 1 s8 |" M- w$ Q* M3 r; p}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>' ^; V/ X O7 H5 [, l0 u h2 J
4 L( K& e( E7 b3 ^! G$ N$ i# L g2()方法现在能够遍历Collection的派生,就像你能够从Map中得到的键值对。事实上,你能够写iterator,它产生数据,代替遍历一个Collection。你能够写iterator,它从测试的框架或者文件中得到信息。这会有巨大的灵活性。 , Y# U" y; Y# x; d3 p% I. a
7 H. F' G( \. l& |! u1 R* \5 H3 D<B>耦合</B> 4 L5 a: ]0 W5 {, U6 M" \7 _% \% n( i9 {( Z7 ~
对于实现继承,一个更加关键的问题是耦合---令人烦躁的依赖,就是那种程序的一部分对于另一部分的依赖。全局变量提供经典的例子,证明为什么强耦合会引起麻烦。例如,如果你改变全局变量的类型,那么所有用到这个变量的函数也许都被影响,所以所有这些代码都要被检查,变更和重新测试。而且,所有用到这个变量的函数通过这个变量相互耦合。也就是,如果一个变量值在难以使用的时候被改变,一个函数也许就不正确的影响了另一个函数的行为。这个问题显著的隐藏于多线程的程序。 & a- F1 {- M, I
) b: x- k5 ]- P) M5 G( O. v
作为一个设计者,你应该努力最小化耦合关系。你不能一并消除耦合,因为从一个类的对象到另一个类的对象的方法调用是一个松耦合的形式。你不可能有一个程序,它没有任何的耦合。然而,你能够通过遵守OO规则,最小化一定的耦合(最重要的是,一个对象的实现应该完全隐藏于使用他的对象)。例如,一个对象的实例变量(不是常量的成员域),应该总是private。我意思是某段时期的,无例外的,不断的。(你能够偶尔有效地使用protected方法,但是protected实例变量是可憎的事)同样的原因你应该不用get/set函数---他们对于是一个域公用只是使人感到过于复杂的方式(尽管返回修饰的对象而不是基本类型值的访问函数是在某些情况下是由原因的,那种情况下,返回的对象类是一个在设计时的关键抽象)。 " H: Z) A! a3 Q+ B6 B- g2 r o3 Q$ T/ K. Z6 ?( F 这里,我不是书生气。在我自己的工作中,我发现一个直接的相互关系在我OO方法的严格之间,快速代码开发和容易的代码实现。无论什么时候我违反中心的OO原则,如实现隐藏,我结果重写那个代码(一般因为代码是不可调试的)。我没有时间重写代码,所以我遵循那些规则。我关心的完全实用—我对干净的原因没有兴趣。 6 t+ K6 D7 p$ Z5 A9 g j) J4 y: B g3 A, }
<B>脆弱的基类问题</B> / K) _, Y, `* G' A
3 ]) b: _! v5 r8 m6 R
现在,让我们应用耦合的概念到继承。在一个用extends的继承实现系统中,派生类是非常紧密的和基类耦合,当且这种紧密的连接是不期望的。设计者已经应用了绰号“脆弱的基类问题”去描述这个行为。基础类被认为是脆弱的是,因为你在看起来安全的情况下修改基类,但是当从派生类继承时,新的行为也许引起派生类出现功能紊乱。你不能通过简单的在隔离下检查基类的方法来分辨基类的变化是安全的;而是你也必须看(和测试)所有派生类。而且,你必须检查所有的代码,它们也用在基类和派生类对象中,因为这个代码也许被新的行为所打破。一个对于基础类的简单变化可能导致整个程序不可操作。 . w& I( B; z, `; g
8 v% l3 K' l! L! ?7 \9 x 让我们一起检查脆弱的基类和基类耦合的问题。下面的类extends了Java的ArrayList类去使它像一个stack来运转: & j: @6 P$ V$ o, T" p/ |
q* k2 k; C2 s8 H& j% i8 B6 i# O
<CCID_NOBR>0 U0 l6 L' \ n: t. ^
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540>% w; n' W* i1 u
. D! t0 k! q4 m, @6 `" `. i6 x
<TR>) s, H( i' H+ W9 B0 i
<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>class Stack extends ArrayList , g; l$ U" n! K# S @% i& Y- b: m9 _5 N{ private int stack_pointer = 0;# k* a) n2 N% P+ ]6 T
9 z4 v4 X: }( I; y7 W- o- R0 i" M
public void push( Object article ) / o m* S0 ?0 q& u2 u { add( stack_pointer++, article ); z; U' M$ w, m* o }6 U$ A# k9 [6 c% j9 T
" T7 C$ [+ z' O, F' O public Object pop() 4 g3 v w0 |9 _+ o+ H3 Q { return remove( --stack_pointer ); . \, n6 ?4 A. f) ~3 W4 D; m }6 C* F9 M; G) X& r6 z
( A7 f* c+ N* ]3 z public void push_many( Object[] articles ) ) @" T! J" X- [/ P6 i W* m; { { for( int i = 0; i < articles.length; ++i )* A' u* x) Y( ]( i! v8 E
push( articles );8 X% `& n& F- g
}$ @* M* c/ l6 }) v8 v( g; }2 z" y
}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR> ' E6 Y; v) i6 p0 { + A. `, s L, V& K" _- m: k* b; }$ r甚至一个象这样简单的类也有问题。思考当一个用户平衡继承和用ArrayList的clear()方法去弹出堆栈时: 8 w/ z6 a8 }( y
. x: |1 l8 d) c0 T- S3 ~
<CCID_NOBR>$ v) P8 U F9 R# w+ r
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540>+ \/ e1 u1 S4 G/ T$ b1 d
7 i8 Y# [( Y/ v8 v- Z* H
<TR> 5 J' g+ B2 n; l2 Q) I; w! T! R s<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>Stack a_stack = new Stack(); + _5 T* O+ f* S" V5 Ca_stack.push("1"); ; F& l I8 R( ]. W2 K8 t8 sa_stack.push("2");1 V6 V' F: K8 [- p( m
a_stack.clear();</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>/ u1 A1 y) T2 ~' n; M7 t8 T6 ^5 m