日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

你可能還記得,Liskov 代換原則是關于承諾和契約的規則。但具體是怎樣的承諾呢?為了確保 subtype(子類型)的安全性,意味著必須保證可以合理地從超類型推導出 subtype,而且這個過程具有傳遞關系。在數學中,對所有 a,b,c ∈ x,如果 aRb 并且 bRc,那么 aRc。在面向對象程序設計中,subclass 即對應 subtype,然而這不是正確的打開方式(這篇文章中 subclass 特指 subtype)。我們必須確保不會違反繼承超類的承諾,我們不會依賴于一些無法控制的東西。如果它發生更改,則可以影響其他對象(這些是不可變對象)。實際上,subclass 甚至可能導致 bug。

譯注:Liskov 于1987年提出了一個關于繼承的原則:“繼承必須確保超類所擁有的性質在子類中仍然成立”。也就是說,只有當一個子類的實例應該能夠替換任何其超類的實例時,它們之間才具有 is-A 關系。

1. 為什么要安全地使用subtype(子類型)

實際上,subclass 是一種特殊的 subtype,它允許 subtype 重用 supertype 的實現(目的是防止因超類中的小改動導致重新實現)。我們可以認為 subclass 是 subtype,但不能說 subtype,但不能說 subtype 是 subclass。subclass 主要有兩個工作:subtype(多態)和代碼重用。subtype 的影響最大,父類中任何 public 或 protected 更改都將影響其子類。subetype 有時候是,但并不總是 Is-A 關系。實際上,subtype 是一種程序上的代碼重用技術,也是一種實現動態多態性(dynamic polymorphism)的工具。

subclass 只關心實現的內容和方式,而非承諾的內容。如果違背了基類承諾會發生什么,如何保證它們之間能夠兼容?即使編譯器也無法理解這種錯誤,留給你的會是代碼中的 bug,比如下面這個例子:

class DoubleEndedQueue {
 void insertFront(Node node) {
 // ...
 // 在隊列的前面插入節點
 }
 void insertEnd(Node node) {
 // ...
 // 在隊列末尾插入節點
 }
 void deleteFront(Node node) {
 // ...
 // 刪除隊列前面的節點
 }
 void deleteEnd(Node node) {
 // ...
 // 刪除隊列末尾的節點
 }
}
class Stack extends DoubleEndedQueue {
 // ...
}

如果 Stack 類希望使用 subtype 實現代碼重用,那么它可能會繼承一個違反自身原則的行為,比如 insertFront。讓我們接著看另一個代碼示例:

public class DataHashSet extends HashSet {
 private int addCount = 0;
 public function DataHashSet(Collection collection) {
 super(collection);
 }
 public function DataHashSet(int initCapacity, float loadFactor) {
 super(initCapacity, loadFactor);
 }
 public boolean function add(Object object) {
 addCount++;
 return super.add(object);
 }
 public boolean function addAll(Collection collection) {
 addCount += collection.size();
 return super.addAll(collection);
 }
 public int function getAddCount() {
 return addCount;
 }
}

上面的示例使用 DataHashSet 類重新實現 HashSet 跟蹤插入操作。DataHashSet 繼承 HashSet 并成為它的一個子類。我們可以在 JAVA 中傳入 DataHashSet 對象替換 HashSet 對象。此外,我的確重寫(override)了基類中的一些方法。這在 Liskov 代換原則中合法嗎?由于沒有對基類行為進行任何更改,只是加入跟蹤插入操作代碼,似乎完全合法。但我認為這顯然是錯誤的 subtype 代碼。

首先,應該看一下 add 方法到底能做什么。它把 addCount 屬性加1,并調用父類方法。這段代碼存在一個溜溜球問題。讓為我們看看 addAll 方法。首先,它把 addCount 的值加上集合大小,然后調用父類的 addAll 方法。但是父類的 addAll 方法具體怎么執行呢?它將多次調用 add 方法(循環遍歷集合)。問題來了,父類將調用哪個 add 方法?是當前子類的 add 還是父類中 add?答案是子類中的 add 方法。因此,count 大小增加兩倍。調用 addAll 時,count 增加一次,當父類調用子類 add 方法時,count 會再增加一次。這就是為什么稱之為悠悠球問題。

譯注:yo-yo problem 溜溜球問題。在軟件開發中,溜溜球問題是一種反模式(anti-pattern)。當閱讀和理解一個繼承層次非常長且復雜的程序時,程序員不得不在許多不同的類定義之間切換,以便遵循程序的控制流程。這種情況經常發生在面向對象程序設計。該術語來自比較彈跳注意的程序員的上下運動的玩具溜溜球。Ganti、Taenzer 和 Podar 解釋為什么這么命名時說道:“當我們試圖理解這些信息時,常常會有一種騎著溜溜球的感覺”。

這里還有一個例子證明 subtype 是有風險的,看下面這段代碼:

class A {
 void foo(){
 ...
 this.bar();
 ...
 }
 void bar(){
 ...
 }
}
class B extends A {
 // 重寫 bar
 void bar(){
 ...
 }
}
class C {
 void bazz(){
 B b = new B();
 // 這里會調用哪個 bar 函數?
 B.foo();
 }
}

調用 bazz 方法時,將調用哪個 bar 方法?當然是 B 類中的 bar 方法。這會帶來什么問題?問題在于 A 類中的 foo 方法不知道被 B 類中 bar 方法重寫,由于 A 中的 foo 方法認為調用的是自己定義的 bar 方法,從而導致類中的不變量和封裝遭到破壞。這個問題也稱為脆弱的基類問題(fragile base-class problem)。

subtype 會引發更關鍵的耦合問題:程序中的一部分對另一部分產生非預期得依賴(緊耦合)。強耦合的經典案例就是全局變量。例如,如果修改全局變量的類型,那么使用該變量(即與變量耦合)的所有函數都可能受到影響,因此必須檢查、修改和重新測試所有代碼。

此外,所有使用全局變量的函數都因為它彼此耦合。也就是說,如果變量的值在某個不恰當的時間更改,那么一個函數可能錯誤地影響另一個函數的行為。在多線程程序中,這種問題尤其可怕。

2. 如何安全地 subclass

subclass 最安全的方法是避免 subtype。如果類設計時并不希望支持 subclass,那么可以把構造函數設為 private 或在類的聲明中加 final 關鍵字防止 subclass。如果希望支持 subclasss,那么可以新建一個包裝類(wrApper class)實現代碼重用。

這時,我們需要對代碼重用進行模塊化推理,即在無需了解實現細節的情況下重用代碼的能力。有幾種方法可以做到這一點,這里介紹其中的一些方法。一種方法是將可重寫(overridable)的功能限制在少數 protected 方法中,避免自我調用可重寫方法。

例如,通過語言自身機制或規范來防止重寫其他方法。在 DataHashSet 類中,避免 addAll 調用 add 方法。另外,避免在類中調用可重寫方法減少重寫對其他函數的直接影響。讓我們用前面的例子繼續說明:

class A {
 void foo(){
 ...
 this.insideBar();
 ...
 }
 void insideBar(){
 ...
 }
 void bar(){
 this.insideBar();
 }
}
class B extends A {
 // 重寫 bar
 void bar(){
 ...
 }
}
class C {
 void bazz(){
 B b = new B();
 B.foo();
 }
}

在上面的代碼中,僅僅添加了 insideBar 方法,防止重寫導致不必要的更改,就可以解決問題。大多數情況下,創建包裝類是降低 subtype 風險的好方法。相比 subtype 我更喜歡組合(composition)或委托(delegation)。

有些時候必須不惜一切代價避免 subtype。如果有不止一種方法實現 subtype,那么最好使用委托。或者父類中包含一些沒有調用的方法時,意味著不需要使用繼承(Liskov 代換原則)。同樣的規則對 class 也適用。我的意思是不應該在啟用共享類(shared class)的時候對重用該類。

譯注:shared class 共享類技術。Java5 平臺的 IBM 實現中新的共享類特性提供了一種完全透明和動態的方法,可以共享已經裝載的所有類,而不會對共享類數據的 JVM 施加限制。這個特性為減少虛擬內存占用和改進啟動時間提供了一個簡單且靈活的解決方案,大多數應用程序都能夠因此受益。

3. subtype 委托

subtype 模式可以把類看做模板,它定義了所有實例的結構。每個實例都具備類屬性與行為,但不包含屬性值。因為類的所有實例(包括子類的實例)都使用類定義的屬性,所以對類屬性的任何更改都將影響到所有實例。

一個實例包含了父類(superclass)和子類所有信息的組合。subtype 呈一種線性的上下關系(Java 與 C++ 不同,不能有多個子類)。值存儲在實例中,而不是類中,并且不支持共享。在子類中,實例之間互相獨立,更改一個實例的狀態值不會影響任何其他實例,而且每個實例都有自己的父對象。

委托表示使用另一個對象進行方法調用。在委托實例中,不通過類共享屬性或行為,通常稱之為無類實例。要重用某個類,可以使用它的一個實例。假設有一個面積計算器類,能夠接受不同形狀并返回其計算的面積。只要創建一個面積計算器對象,調用不同的面積計算類。在子類中,針對每種類型的面積計算,必須創建一個帶有父類型的獨立對象。

如果計算器對象將一個方法或變量委托給一個原型,那么修改任何屬性或值都將同時影響對象和原型。使用這種方式,委托關系中的對象會互相依賴。在委托實現中,需要啟動多個對象。與 subtype 相反,對象可以是不同類型。此外,還需要用正確的方式組合實例,以滿足類的需要。

由于沒有父類,因此不能直接使用對象屬性。在 subtype 中,子類可以使用父類中的屬性或方法;在委托中,必須先定義才能訪問。

在委托中,只需要建立同這些類的連接,一個重用類(reuse class)可以重復使用多個重用類,這些類都包含在同一個實例中。但在 subtype 中,重用類必須是其他重用類的子類(具備繼承關系)。

讓我們用委托來解決 DataHashSet 中的問題:

public class DataHashSet implements Set {
 private int addCount = 0;
 private Set set;
 public
 function DataHashSet(Set set) {
 This.set = set;
 }
 public boolean
 function add(Object object) {
 addCount++;
 return This.set.add(object);
 }
 public boolean
 function addAll(Collection collection) {
 addCount += collection.size();
 return This.set.addAll(collection);
 }
 public int
 function getAddCount() {
 return addCount;
 }
}

4. 如何使用 Skeletal 模式?

Skeletal 模式既不損失靈活性,又能享受 subtype 的優點。它為每個接口提供一個實現該接口的抽象類,不指定基礎方法(primitive method)。這意味著將方法設為 abstract 由子類實現,同時它還定義了非基礎方法。然后,由使用該接口的開發者實現接口,負責框架實現。它不如包裝類靈活,比如組合或委托。為了增加其靈活性,可以使用包裝類將調用委托給框架實現的匿名子類對象。

分享到:
標簽:類型 Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定