JWorld@TW the best professional Java site in Taiwan
      註冊 | 登入 | 全文檢索 | 排行榜  

» JWorld@TW » Software Design » Effective Java  

按列印兼容模式列印這個話題 列印話題    把這個話題寄給朋友 寄給朋友    訂閱主題
reply to topicthreaded modego to previous topicgo to next topic
本主題所含的標籤
無標籤
作者 [Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承
metavige

metavige

版主

發文: 2133
積分: 10
於 2004-05-26 22:16 user profilesend a private message to usersend email to metavigereply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
除非專為繼承而設計並提供文件,否持不要使用繼承
這是這個條款開宗明義的一句話,也說明了繼承這件事情是必須要特別被注意的.....

首先,這樣的class必須提供文件,準確描述覆寫(overriding)任何函式的效果。換句話說,class必須說明他對於可被覆寫函式(overridable methods)的自用(self-use)程度。
為了詳細說明一個class,使他得以被安全地subclassed,你必須描述其實作細節。


為了繼承而進行的設計,不僅只是加上自用(self-use)形式的詳細文件而已。class必須審慎挑選要做為內部活動互動的protected函式,這被稱為hook。

當你針對繼承設計出一個可能被廣泛使用的class時,你應該意識到你是對文件中說明的自用(self-use)形式承擔了一份永久的義務,也是一種承諾,這些承諾會影響後續版本可能會很難提升class的效率或功能。

class還必須遵守一些限制規定,才能被用於繼承機制:無論直接或間接,建構式絕對不能呼叫可被覆寫函式(overridable methods)。
舉個例子:
1
2
3
4
5
6
7
8
public class People {
  public People() {
    showName();
  }
  public void showName() {
    System.out.println("I don't know.....");
  }
}

當我去覆寫showName(),就會發生問題.....
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Man extends People {
  private final String name;
  public Man() {
    this.name = "metavige";  
  }
  public showName() {
    System.out.println(this.name);
  }
  public static void main(String[] args) {
    Man metavige = new Man();
    metavige.showName();
  }
}

這樣showName()就會被呼叫兩次,而第一次呼叫的時候,name還未被設定∼

Cloneable & Serialiable 這兩個介面給繼承設計帶來了特別的困難。
因為clone() & readObject()的行為很像建構式,因此受到同樣的約束:無論直接或間接,clone() & readObject()絕對不能呼叫可被覆寫函式(overridable methods)。


其實,為了繼承而設計的class必須要很小心,因為可覆寫的機制,很容易因為覆寫,導致subclass的邏輯性發生問題,所以解決這類方法的最好辦法是:
對於那些並非「專門為了被安全地子類別化(subclassed)並攜帶相關文件」的class,禁止對他進行子類別化。


審慎的決定為了繼承而設計的class之實作細節,會對以後的實作發展有很大的影響。
像是最近因為專案的關係,有了設計相關class的機會,第一個subclassed的class出現的時候,並不會有太大的感覺,但當第十個、第一百個class出現的時候,就會有很深刻的體認,尤其是在團隊開發的時候,影響尤其深刻。


reply to postreply to post
請各位新手參考 論壇規範Java 新手 FAQ
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:metavige]
plutotw

井底蛙



發文: 624
積分: 3
於 2004-05-26 22:37 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
繼承算是 雙刃之劍 . 用的好的話,程式碼簡潔,效率極高 ..
但是該不該用 繼承 , 郤是難以用臨界點隔開 ...
尤其 JAVA 與 C++ 的 protected/default 定義不同,造成 更多問題
對需要封裝的 data 及 method 而言,算是較不好
還好現在的工具, ex: eclipse 可以看到 該 method 是否是繼承/實作 上層的 method
個人的經驗,如果能用繼承,就儘量用繼承實作


reply to postreply to post
任何聰明的傻瓜都可以讓事情更大、更複雜、更激烈。要往反方向發展需要一絲天分以及許多勇氣。-愛因斯坦
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:plutotw]
ingramchen

Web monkey



發文: 479
積分: 12
於 2004-05-26 23:29 user profilesend a private message to usersend email to ingramchenreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
plutotw wrote:
繼承算是 雙刃之劍 . 用的好的話,程式碼簡潔,效率極高 ..
但是該不該用 繼承 , 郤是難以用臨界點隔開 ...
尤其 JAVA 與 C++ 的 protected/default 定義不同,造成 更多問題
對需要封裝的 data 及 method 而言,算是較不好
還好現在的工具, ex: eclipse 可以看到 該 method 是否是繼承/實作 上層的 method
個人的經驗,如果能用繼承,就儘量用繼承實作


eclipse 是可以看到該 method 是否 override 上層的,不過當繼承的結構到了三層以上時,管理上就會開始有點困難 (哪些 method override 最上層?哪些 method override 第二層?改了第一層,是第二層受影響還是第三層受影響?) 我自己的經驗是大多數的class 最多只繼承一次,再多就考慮用 delegation 或是 decorator 的方式來實作。 以前曾經寫過四層的繼承關係,的確,這樣享有最大的 code reuse。但好處也僅止於此,整個 class 階層再也動不了了,override 的關係也是亂的一踏胡塗,maintain 變得很痛苦....


reply to postreply to post
MyBlog | 訂便當管理系統

作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:ingramchen]
plutotw

井底蛙



發文: 624
積分: 3
於 2004-05-27 00:44 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
ingramchen wrote:
eclipse 是可以看到該 method 是否 override 上層的,不過當繼承的結構到了三層以上時,管理上就會開始有點困難 (哪些 method override 最上層?哪些 method override 第二層?改了第一層,是第二層受影響還是第三層受影響?) 我自己的經驗是大多數的class 最多只繼承一次,再多就考慮用 delegation 或是 decorator 的方式來實作。 以前曾經寫過四層的繼承關係,的確,這樣享有最大的 code reuse。但好處也僅止於此,整個 class 階層再也動不了了,override 的關係也是亂的一踏胡塗,maintain 變得很痛苦....

eclipse 是單一 class 可見有取代上層而已, 但是可以另由繼承表看,每一單層都可以獨立檢視
繼承 層度夠深時,確是這個樣子 ...
但是繼承 應該 是視需要的來做 ...
而不是為繼承而繼承 , 繼承夠深時,就應考慮 是否 要將 譬如
第四層直接用繼承第一層來寫
或第二三層合併
基本上繼承再多層,都不會有困難才是 , 因為 java 的 super. 僅能使用上一層
無法跨層 , C++ 則不同,可以指定跳層 call method (不過算自由吧)
用 java 寫繼承時,應儘量只注重在 上一層 的狀態及 method ,
不應有跨層的想法 , 除非像是 paint() ;
這樣依序下來,狀況會比非繼承寫法來簡單


reply to postreply to post
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:plutotw]
Yoshi

塵世中一個迷途小書僮

版主

發文: 874
積分: 22
於 2004-05-27 02:47 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
plutotw wrote:
繼承算是 雙刃之劍 . 用的好的話,程式碼簡潔,效率極高 ..
但是該不該用 繼承 , 郤是難以用臨界點隔開 ...
尤其 JAVA 與 C++ 的 protected/default 定義不同,造成 更多問題
對需要封裝的 data 及 method 而言,算是較不好
還好現在的工具, ex: eclipse 可以看到 該 method 是否是繼承/實作 上層的 method
個人的經驗,如果能用繼承,就儘量用繼承實作


最後這句話和我目前所上的課看法完全相反耶....
我不停的看到"Favor Composition Over Inheritance"這句話
也因此我自己寫程式都很少用繼承,大多是delegation~

繼承就我個人粗淺的認識,有些情況下看似應該使用繼承,但實際上不是
比如矩型與正方型,我很直觀的認為正方型理當繼承矩型
而大家也會覺得很合理,setWidth時裡面也順便setHeight,setHeight則反之
但是這個看似合理的動作,在單看Square時是對的,但是在與其它class互動就又會有問題
這學期上軟工有關的課時被騙的題目...

我知道用來判斷"該不該繼承"的方法,有
Coad's Rule:
1
2
3
4
5
* A subclass expresses "is a special kind of" and not "is a role played by a"
* An instance of a subclass never needs to become an object of another class
* A subclass extends, rather than overrides or nullifies, the responsibilities of its superclass
* A subclass does not extend the capabilities of what is merely a utility class
* For a class in the actual Problem Domain, the subclass specializes a role, transaction or device


以及Liskov Substitution Principle
1
2
3
4
5
*The Liskov Substitution Principle (LSP) makes it clear that the ISA relationship is all about behavior
* In order for the LSP to hold (and with it the Open-Closed Principle) all subclasses must conform to the behavior that clients expect of the base classes they use
* A subtype must have no more constraints than its base type, since the subtype must be usable anywhere the base type is usable
* If the subtype has more constraints than the base type, there would be uses that would be valid for the base type, but that would violate one of the extra constraints of the subtype and thus violate the LSP!
* The guarantee of the LSP is that a subclass can always be used wherever its base class is used!


我附上這個的pdf檔,裡面有一步步分析該不該繼承,
從一開始認為應該繼承,到後來的分析才漸漸發現不適合的過程。
Square不該繼承Rectangle的例子也在裡面~

參考看看囉~

OOPrinciples.pdf (112.61k)


Yoshi edited on 2004-05-27 02:49
reply to postreply to post
YOSHI!
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:Yoshi]
plutotw

井底蛙



發文: 624
積分: 3
於 2004-05-27 04:59 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Yoshi wrote:
最後這句話和我目前所上的課看法完全相反耶....
我不停的看到"Favor Composition Over Inheritance"這句話
也因此我自己寫程式都很少用繼承,大多是delegation~

從一開始認為應該繼承,到後來的分析才漸漸發現不適合的過程。
Square不該繼承Rectangle的例子也在裡面~

1
2
3
4
5
6
7
8
9
10
11
 public class TestRectangle {
// Define a method that takes a Rectangle reference.
public static void testLSP(Rectangle r) {
 r.setWidth(4.0);
 r.setHeight(5.0);
 System.out.println("Width is 4.0 and Height is 5.0" + ", so Area is " + r.area());
 if (r.area() == 20.0) 
  System.out.println("Looking good!\n");
 else
  System.out.println("Huh?? What kind of rectangle is this??\n");
}

你難道不會覺得被誤導嗎 ?
首先 方形本來就較簡單 , 方形只能有一個值
使用者故意將方形的邊設定兩次,當然是以後者為準呀 !
你總不能 寄望 將值設錯 還期望 class 能正常運作吧!
其二
在架構上 ,簡單的要在前面,因為應該可以是
public class Line extends Point ;
public class Square extends Line ;
public class Rectangle extends Square ;
而且在這條件下,還可指定 data 值為 final 屬性
而事實上,因為特性的關係 Square 這一層是可以不存在的
因為 Rectangle 本身就可以取代他
其三
如果 應要 保持 文中所說的架構
也應該在 setWidth() setHeight() 內部作檢查,並擲出 Exception

比較好的 代表是 Circle 與 Arc 的關係

軟工裡會將 OOP 最重要的繼承打掉,是要考慮做苦工的 coding , 要重點放在 SA/SD 處理掉
但是有一些領域 ,是連 SA/SD 都無法完成的領域
此時 programmer 的功力將是非常吃重


plutotw edited on 2004-06-11 21:04
reply to postreply to post
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:metavige]
giraffe





發文: 11
積分: 0
於 2004-06-18 11:21 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
我個人比較贊成 plutotw 所説,主要的原因是繼承對 caller 而言所須 allocate 的 object 較 delegation 為少,allocate object 基本上是一個高成本動作,雖然繼承會造成的依賴性較多,程式碼也較難維護,但是個人認為盡量去用是正確的方針 。樓上的那個例子,用長方型設單邊長度的 method 去設正方型的兩邊,已經違反了繼承的原則。是一個不理想的例子。

reply to postreply to post
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:giraffe]
ingramchen

Web monkey



發文: 479
積分: 12
於 2004-06-18 15:03 user profilesend a private message to usersend email to ingramchenreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
giraffe wrote:
我個人比較贊成 plutotw 所説,主要的原因是繼承對 caller 而言所須 allocate 的 object 較 delegation 為少,allocate object 基本上是一個高成本動作,雖然繼承會造成的依賴性較多,程式碼也較難維護,但是個人認為盡量去用是正確的方針 。樓上的那個例子,用長方型設單邊長度的 method 去設正方型的兩邊,已經違反了繼承的原則。是一個不理想的例子。


這個例子當然很容易看出來問題在哪裡,它是專用來解釋 LSP 現象的例子啊。
實務的系統就沒那麼簡單了。首先要判斷 domain 的繼承關係 就不是件容易的
事 ( Domain Knowledge 要夠才行) 再加上動輒數百個 class 的規模,複雜度
可不是這麼好釐清的。又或者系統已經上線一陣子了,今天使用者突然來個
special case ,大多數的狀況都會選擇延伸舊有的 class hierarchy (因為快
,而且不用動大刀去大改已經穩固的架構) 久而久之 LSP 的現象就蔓延開來而
不自知。

就拿 plutotw 臨時提的範例好了:
public class Line extends Point ;
public class Square extends Line ;
public class Rectangle extends Square ;

如果使用者多了一個三角形的需求怎麼辦?要像下面這樣子繼續繼承下去嗎?
public class Line extends Point ;
public class Triangle extends Line ; //三角形..
public class Square extends Line ;
public class Rectangle extends Square ;

再多個五角形、六角形..... 然後 class hierarchy 越長越肥,太多層就沒辦法控管了

如果一開始就選擇 composite:
1
2
3
4
5
6
7
8
9
10
public class Point ;
public class Line {
   Point[] ends = new Point[2];
}
public class Triangle {
   Line[] sideLengths = new Line[3];
}
public class Square {
   Line[] sideLengths = new Line[4];
}


這樣有彈性多了,往後要怎麼加變化都簡單,甚至多增加到立方體都沒問題。

我覺得理論上繼承都可以做的很漂亮,但是實務上程式的可維護性更重要,你不會有太多的
時間去做大的 refactoring來維持繼承的合理性,而且維護的人可能並不是原本的設計者,
他可能會誤用你當初設計好的理念。所以 effective java 才會建議,要用繼承 document
一定要解釋清楚啊。


reply to postreply to post
MyBlog | 訂便當管理系統

作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:ingramchen]
plutotw

井底蛙



發文: 624
積分: 3
於 2004-06-18 17:36 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
ingramchen wrote:
如果使用者多了一個三角形的需求怎麼辦?要像下面這樣子繼續繼承下去嗎?

再多個五角形、六角形..... 然後 class hierarchy 越長越肥,太多層就沒辦法控管了

如果一開始就選擇 composite:

因為這是個假想的 case ,所以很難說怎樣,
但是實作上也不會這樣作,應該是
1
2
3
4
5
6
public class Polygon {
   Point[] pts ;
Poyygon( int size ) {
 pts = new Point[ size ];
}
}

真的有需要的話
1
public class Square extends Polygon  ;

其實以這個來說, 要應付實際的 case 有多種想法,但是主要是要善用繼承
而不要人家說繼承不好用,不好寫,問題很多
(如果說的人真的將繼承用的達到瓶頸的話,但實際上....)
就一開始放棄繼承的想法 . 只是限制自己的發展
至於維護,如果 後續維護的人功力差太多,那當然..
只是這已跟是否繼承無關,反而是變成維護銜接的問題了
當然您之前提的繼承三四層的問題,這還好吧,我做的在主要的 UI 控制部份(visual tool 拉不出來的) 也有超過這個深度,只是每一層的變化不會很大就是(變化很大也許就不應用繼承)


plutotw edited on 2004-06-18 18:13
reply to postreply to post
作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:plutotw]
ingramchen

Web monkey



發文: 479
積分: 12
於 2004-06-18 19:49 user profilesend a private message to usersend email to ingramchenreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
嗯, Polygon 若refactor改採 composite 的方式來實做會比較有彈性。 Polygon 其實 extends Point 也可以做的出來,但我們不會 "因為可以用 繼承實做,就盡量用繼承"。

另外這個 Polygon 的需求是後來才蹦出來的 (也許是一年後吧...),原本的設計只有 square / rectangular 需求,當初設計成 point extends line 再 exnteds square 並沒有錯啊,而且也才三層而已,使用上不會多困難。然而現在需求多了個 polygon。如果不用 extends ,而改採 refactor 成 polygon 那會花多少精神時間?

在我的觀點裡是 maintain 比較重要啦... 而且繼承要做 refactoring 和 test 難度都比較高。所以習慣是 composite 優先於 繼承,除非頂多繼承一層而已。也許這種習慣只適用現在的 Java 而已....

個人淺見。


reply to postreply to post
MyBlog | 訂便當管理系統

作者 Re:[Effective Java] 條款 15 : 除非專為繼承而設計並提供文件,否則不要使用繼承 [Re:ingramchen]
plutotw

井底蛙



發文: 624
積分: 3
於 2004-06-18 20:51 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
ingramchen wrote:
嗯, Polygon 若refactor改採 composite 的方式來實做會比較有彈性。 Polygon 其實 extends Point 也可以做的出來,但我們不會 "因為可以用 繼承實做,就盡量用繼承"。

當初在舉出 polygon 時,只是隨便舉個例子,所以 沒有 從 Point 繼承下來用
其實 用 繼承 有很多原因,如資料是依續擴展的, 或需實作 多形 ...
附圖 是我實作 AutoCad DXF 格式的繼承圖


plutotw edited on 2004-06-18 20:58
reply to postreply to post
» JWorld@TW »  Software Design » Effective Java

reply to topicthreaded modego to previous topicgo to next topic
  已讀文章
  新的文章
  被刪除的文章
Jump to the top of page

JWorld@TW 本站商標資訊

Powered by Powerful JuteForum® Version Jute 1.5.8