& k& _" V2 {" `7 V# e, z* z 这里,我不是书生气。在我自己的工作中,我发现一个直接的相互关系在我OO方法的严格之间,快速代码开发和容易的代码实现。无论什么时候我违反中心的OO原则,如实现隐藏,我结果重写那个代码(一般因为代码是不可调试的)。我没有时间重写代码,所以我遵循那些规则。我关心的完全实用—我对干净的原因没有兴趣。 , L' K! S; a. m' x8 o8 u& G; z3 W z2 M9 X" o7 C+ P# h
<B>脆弱的基类问题</B> ( T k0 v7 y& @7 l# _2 Q! x
& I/ S# h. Y% c 现在,让我们应用耦合的概念到继承。在一个用extends的继承实现系统中,派生类是非常紧密的和基类耦合,当且这种紧密的连接是不期望的。设计者已经应用了绰号“脆弱的基类问题”去描述这个行为。基础类被认为是脆弱的是,因为你在看起来安全的情况下修改基类,但是当从派生类继承时,新的行为也许引起派生类出现功能紊乱。你不能通过简单的在隔离下检查基类的方法来分辨基类的变化是安全的;而是你也必须看(和测试)所有派生类。而且,你必须检查所有的代码,它们也用在基类和派生类对象中,因为这个代码也许被新的行为所打破。一个对于基础类的简单变化可能导致整个程序不可操作。 . g1 ~# ?$ q# k' [
7 \: o D1 E5 n$ z
让我们一起检查脆弱的基类和基类耦合的问题。下面的类extends了Java的ArrayList类去使它像一个stack来运转: - e: P- h/ o, z. |/ F
# }5 d) B! x) ^" L, \. i<CCID_NOBR> & f* a2 q8 @8 I( w<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540>, p& x( N1 |% o; e. O
) O+ n* [( U- A/ H& R
<TR> 1 H2 ]9 g" n/ E3 e& Q6 x" i<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>class Stack extends ArrayList 3 Q9 A% _2 G1 G{ private int stack_pointer = 0;# @6 l9 O/ n7 Y: N! E1 l
T$ l( g. d- x6 |
public void push( Object article )" ~2 E* e. U8 j, Z3 v. g& J4 C
{ add( stack_pointer++, article ); ( i9 V. m, t! p6 l' X! W2 F } , X, k) @, |% m, p 2 \/ u6 E q# e" {3 t1 V- d public Object pop() : o7 B8 ^/ Q- |$ A% e+ Y; L9 ?2 R9 | { return remove( --stack_pointer ); \) t* q& _6 H/ @( y5 L$ {
} ( G' c$ A2 s1 n/ b3 ~ 7 b B, Z r' K" Z# N public void push_many( Object[] articles ) 8 a* ?& B6 F7 r, y( { q { for( int i = 0; i < articles.length; ++i ) 0 Q) z/ E+ c# n& Y: K2 N' Q push( articles ); / ~- J+ N7 Q9 H }% d, V" U$ d7 v! r4 y( {, g( {
}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>9 y, t- w% Y/ o3 B q+ O3 F" e
# j1 y1 d$ m6 U6 ^
甚至一个象这样简单的类也有问题。思考当一个用户平衡继承和用ArrayList的clear()方法去弹出堆栈时: ' c# C+ B' x, n/ U; Z + |- ^- Q/ {% j7 p" e& z7 x' z<CCID_NOBR> 1 V; L# r6 q6 J# u, u: i<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> 2 {% z; A' V) G. F: P% S4 |3 A' K1 Z* D% o8 ?* [5 F
<TR> 4 |) b) K) f1 D7 e- n F" Z( m: s- P. l<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>Stack a_stack = new Stack(); 1 L7 X0 f) ^. ?( _+ f' @0 [$ P' Y2 ya_stack.push("1"); 4 ]- O/ p- |) }1 @a_stack.push("2");' j9 y6 [; v$ T% e+ N# m" R* I
a_stack.clear();</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>% H( D2 R+ n+ [0 v6 j/ n" s
- G5 r3 N' \- U$ A7 N
这个代码成功编译,但是因为基类不知道关于stack指针堆栈的情况,这个stack对象当前在一个未定义的状态。下一个对于push()调用把新的项放入索引2的位置。(stack_pointer的当前值),所以stack有效地有三个元素-下边两个是垃圾。(Java的stack类正是有这个问题,不要用它). ! ]5 z7 ?8 V5 T; [4 w+ z! y
( a% L; q) k. w Y- @
对这个令人讨厌的继承的方法问题的解决办法是为Stack覆盖所有的ArrayList方法,那能够修改数组的状态,所以覆盖正确的操作Stack指针或者抛出一个例外。(removeRange()方法对于抛出一个例外一个好的候选方法)。 ( B, \( E0 @3 @, m% @" l# E6 b! z) M9 K w( q* a5 |+ g+ t- A3 y/ z- L
这个方法有两个缺点。第一,如果你覆盖了所有的东西,这个基类应该真正的是一个interface,而不是一个class。如果你不用任何继承方法,在实现继承中就没有这一点。第二,更重要的是,你不能够让一个stack支持所有的ArrayList方法。例如,令人烦恼的removeRange()没有什么作用。唯一实现无用方法的合理的途径是使它抛出一个例外,因为它应该永远不被调用。这个方法有效的把编译错误成为运行错误。不好的方法是,如果方法只是不被定义,编译器会输出一个方法未找到的错误。如果方法存在,但是抛出一个例外,你只有在程序真正的运行时,你才能够发现调用错误。 , L& Z y z. @% D( a; Z" t- y
5 s8 r" H: ^ b, n& u 对于这个基类问题的一个更好的解决办法是封装数据结构代替用继承。这是新的和改进的Stack的版本: , v; [2 A& U6 k5 @; e: a 3 T% `9 j! e n<CCID_NOBR>- K/ g8 g5 W% ]( v" |
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> $ K+ Q* S y: W# A4 O4 l4 k- C" r# ]; Z
<TR> # H4 q& p; k8 X ^4 S<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>class Stack / j4 a1 c7 e7 |* t2 Z{ ( u) P- A9 R& M# N# r8 `& U; p2 A2 | private int stack_pointer = 0; 4 e8 s4 j* v2 W private ArrayList the_data = new ArrayList();4 w# |+ r/ b- i# g% b
; x o3 x- a( W# E" U
public void push( Object article ) W' u: L$ N ]5 W) d8 r7 q{ ! x0 v( Y% M$ N- y6 Q# f the_data.add( stack_poniter++, article ); 0 w/ r& k/ D0 {1 P} ; g T- q6 Y. P) i4 H& ?* b) A+ V! C5 k% v/ Y" K
public Object pop() 4 g8 \# o* |2 J0 k3 ^ A O. J- P{ ( T' f3 @+ S; q" ?. J' d1 [ return the_data.remove( --stack_pointer );+ f8 ?# P4 o( L( `4 [
} 2 g/ L5 M7 Q# b ` 1 B9 a, D: O. o7 N: epublic void push_many( Object[] articles )# ?; [ J# G6 `. b6 j. l
{ , L& ~, @" U: z' T% c for( int i = 0; i < o.length; ++i )( g4 R) v' U, ?* a' O7 {) c
push( articles ); 2 z7 @9 o- p, C N% m! {: _4 C1 B} * g; f2 p `4 z/ l}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR> e; ~; g- N/ j* [; K # e# ^* v: }0 }3 V/ J 到现在为止,一直都不错,但是考虑脆弱的基类问题,我们说你想要在stack创建一个变量, 用它在一段周期内跟踪最大的堆栈尺寸。一个可能的实现也许象下面这样: " W9 E' l) A3 T- Z( F% `3 y& [- e7 o" \. V- j2 c' H( y$ R# J* I
<CCID_NOBR>5 `- ]+ R- ^2 }0 X( x
<TABLE align=center border=1 borderColorDark=#ffffff borderColorLight=black cellPadding=2 cellSpacing=0 width=540> 9 `0 x6 O4 T( s0 D & K3 E2 |- F" L& t5 }1 B- q" T<TR>, O& o# Z+ Q; F3 F. i7 q
<TD bgColor=#e6e6e6 class=code><RE><CCID_CODE>class Monitorable_stack extends Stack9 s+ k/ o! M5 C- O* P0 c1 u4 H, d% o! ~4 q5 N
{6 J- Y% s- u$ V. o! B
private int high_water_mark = 0;2 {6 ?' C. L, P% }
private int current_size; * P7 I% o! {! b6 H$ K 8 `- e8 x+ U$ P) Y" P public void push( Object article ) - ?$ |2 }/ s- ?* C5 D9 n5 o { 7 V M4 Q5 z( `# d* B" S9 w5 v if( ++current_size > high_water_mark ) $ U4 I F8 t5 b9 T2 _- h high_water_mark = current_size; 6 {/ `! U3 w! X. }" F( u super.push( article );6 s+ g" |6 \5 S) A1 K6 h
} $ h. _' m' a' m8 i) S( m & @' F; n* n3 z8 o8 f$ D publish Object pop()& _. s# H7 v- c: F5 p
{: l2 x* w6 X) E* C. u" C
--current_size;; v8 K9 G6 i$ l1 ]+ Y2 N
return super.pop(); 3 [$ W4 Z9 {* j' Q' V, L* V }" g4 O+ t8 p R: a2 o9 H, ~6 `
/ h7 G7 c, ^% c& A& r2 O1 X0 p, ~ public int maximum_size_so_far()( O) A% s. o% d, v, _! @2 ]8 e
{ ; `2 V) g( y( ~- n% N- v return high_water_mark; ! ?7 R+ U$ q2 P8 r3 M8 h } 2 e9 I$ |' `6 J7 ^}</CCID_CODE></PRE></TD></TR></TABLE></CCID_NOBR>7 ~1 c. L& @- e k7 K1 z
' s- e$ @2 f' H& v
这个新类运行的很好,至少是一段时间。不幸的是,这个代码发掘了一个事实,push_many()通过调用push()来运行。首先,这个细节看起来不是一个坏的选择。它简化了代码,并且你能够得到push()的派生类版本,甚至当Monitorable_stack通过Stack的参考来访问的时候,以至于high_water_mark能够正确的更新。