在前面一條中,我們已經知道了David寫了A類被Tom拿去繼承了,導致了A類的封裝性遭到了破壞,那么有沒有可能做點事情避免此事發生呢?第十九條孕育而生!David在創建A類的時候寫上文檔說明,說Al類不允許任何類來繼承,Tom看到后就不會這么做了,除非Tom喜歡寫獅山代碼,一上來不看別人別的文檔說明,一上來就操作猛如虎(這類人現實中其實挺多的)。
好的API應該描述一個給定的方法做了什么工作,而不是描述他是如何做到的。
那么,當你為了繼承而設計的類的時候,如何決定應該暴露那些受保護的成員呢?遺憾的是,并沒有神奇的法則可供你使用。唯一的方法就是測試。
要測試一個為繼承而設計的類,唯一的測試方法就是編寫子類。經驗表明,3個子類通常就足可以測試一個可擴展的類。
當設計一個可能被廣泛使用的用于繼承的類時,要意識到,我們對寫在文檔中的方法的自身使用情況,以及隱含在受保護的方法和字段的實現決策做出了永久性的承諾。這些承諾可能會使在隨后的版本中改進這個類的性能或功能變得困難,甚至不可能。因此,在發布之前必須通過編寫子類來測試。
還有些允許繼承的類必須遵守的限制。構造器不得直接或者間接調用可重寫的方法。違反這個規定,有可能導致程序失敗。超類的構造器會在子類的構造器之前運行,所以子類重寫的方法會在子類構造器之前被調用。
例子:
public class Super{//存在問題 構造器調用了一個可重寫的方法public Super() {overrideMe();}public void overrideMe(){}
}
public final class Sub extends Super{// 一個空的final字段,由構造器設置private final Date date;public Sub(){date=new Date();}//超類構造器調用的重寫方法@Overridepublic void overrideMe(){System.out.println(date);}public static void main(String[] args){Sub sub=new Sub();sub.overrideMe();}
}
結果:
Connected to the target VM, address: '127.0.0.1:51415', transport: 'socket'
null
Wed Jun 26 21:52:51 CST 2024
Disconnected from the target VM, address: '127.0.0.1:51415', transport: 'socket'Process finished with exit code 0
你可能會期待這個程序會打印出日期倆次,但是它第一次打印出的是null,因為overrideMe方法被Super構造器調用的時候,構造器Sub還沒有機會初始化Date域。
在為了繼承而設計的類的時候,Cloneable和Serializable接口出現了特殊的困難。如果類是為了繼承而被設計的,無論實現這其中的那個接口通常都不是一個好主意,因為他們它一下實質性的負擔轉嫁到擴展這個類的程序員的身上。
如果你決定在一個為了繼承而設計的類中實現Cloneable或者Serializable接口,就應該意識到,因為clone和readObject方法在行為上非常類似于構造器,所以類似的限制規則也是使用的:無論是clone還是readObject,都不可以調用可覆蓋的方法,不管是以直接還是間接的方式。
如果你決定在一個為了繼承而設計的類中實現Serializable,并且該類有一個readResolve或者writeReplace方法,就必須使readResolve或者writeReplace成為受保護的方法,而不是私有的方法。
現在我們很清楚了,設計一個用于繼承的類需要付出巨大的努力,對類本身也是很大的限制。
解決這個問題的最佳方案是,對于并非為可以安全地子類化而設計并提供文檔說明的類,禁止對其子類化。
所有文章無條件開放,順手點個贊不為過吧!
? ? ? ? ? ? ? ? ? ? ? ?