簡體   English   中英

確保子類的正確性?

[英]Ensure correctness in sub-class?

如果啟用子類中的覆蓋,如何確保結構的正確性?

或者,換句話說,我們必須確保這一點嗎? (如果否,請給出充分的理由。)


這是顯示問題的一個簡單示例:

我們的結構:一個僅包含偶數ArrayList

庫開發人員Alice編寫了這個(正確的)類:

import java.util.ArrayList;

public class EvenArrayList {
    private ArrayList<Integer> mArrayList = new ArrayList<Integer>();

    public boolean isEven(int num) { return num % 2 == 0; }

    public void addToList(int num) { if (isEven(num)) mArrayList.add(num); }

    public void printList() { System.out.println(mArrayList); }
}

但是,邪惡的Bob用以下( 錯誤 )子類覆蓋了它:

public class EvilEvenArrayList extends EvenArrayList {

    @Override
    public boolean isEven(int num) { return true; }

}

使用后,Mandy選擇Alice的(正確)類:

public class Main {
    public static void main(String[] args) {
        EvenArrayList eal = new EvenArrayList();
        eal.addToList(1);
        eal.addToList(2);
        eal.addToList(3);
        eal.addToList(4);
        eal.addToList(5);
        eal.printList();       // prints [2, 4]
    }
}

使用后,Nancy需要一些額外的功能,因此她選擇了Bob的( 錯誤的 )子類:

public class Main {
    public static void main(String[] args) {
        EvilEvenArrayList eeal = new EvilEvenArrayList();
        eeal.addToList(1);
        eeal.addToList(2);
        eeal.addToList(3);
        ((EvenArrayList) eeal).addToList(4);
        ((EvenArrayList) eeal).addToList(5);
        eeal.printList();      // prints [1, 2, 3, 4, 5]
    }
}

如我們所見,即使是Nancy也明確地將其轉換回父類EvenArrayList ,也無濟於事。


到目前為止,我想到了避免這種情況的解決方案(而Alice開發了自己的庫)。 但是,這不是一個好的解決方案。

(1)不調用其他方法:

public class EvenArrayList {
    // ...

    public boolean isEven(int num) { return num % 2 == 0; }

    public void addToList(int num) { if (num % 2 == 0) mArrayList.add(num); }

    // ...
}

(2)或者,僅使用私有方法(因為它們不會被繼承):

public class EvenArrayList {
    // ...

    public boolean isEven(int num) { return _isEven(num); }
    private boolean _isEven(int num) { return num % 2 == 0; }

    public void addToList(int num) { if (_isEven(num)) mArrayList.add(num); }

    // ...
}

(3)或者,僅使用最終方法(因為它們不能被覆蓋):

public class EvenArrayList {
    // ...

    public boolean isEven(int num) { return _isEven(num); }
    public final boolean _isEven(int num) { return num % 2 == 0; }

    public void addToList(int num) { if (_isEven(num)) mArrayList.add(num); }

    // ...
}

三種方法大致相同。

(1)不太好,因為我們無法重用相同的代碼塊。

(2)和(3)不太好,因為它們通過使方法之間的幾乎兩倍加倍使類復雜化。 而且,它為我們自己創建了一個“陷阱”,我們可能會錯誤地使用可重寫的方法。

另外,(2)和(3)引入了一點函數調用開銷。


這個問題可能是

(1)安全問題:

如果Bob故意錯誤地重寫了某些方法,並誘使其他人使用他的添加的(邪惡的)功能庫,並且Nancy仍然被黑,即使她認為自己正在通過轉換使用Alice的方法版本。

(2)和圖書館開發人員的潛在陷阱:

即使我們同時扮演Alice和Bob的角色,子類中的錯誤覆蓋也可能破壞整個結構(甚至是ArrayList),並且錯誤的原因也可能不容易被發現,因為錯誤發生的位置與原因相去甚遠(我們永遠不會使用時調用isEven() )。


你有什么意見? 請提供一些解決方案。 非常感謝您的投入!

首先,如果您不想讓您的類被子類化,或者您不希望對其進行設計以進行子類化,則可以(並且應該)使其最終化,或者至少記錄該類可擴展的事實。僅供內部使用,不應擴展。

現在,如果您的目標是對該類進行子類化,則確實必須對其進行設計,以使子類不會輕易破壞超類的不變式。 那是一項艱巨的任務。

在您的示例中,沒有理由允許重新定義isEven() ,因此您應該簡單地將其聲明為final(如果您想將其公開),或者將其私有(如果您不想將其公開)。 私有方法是隱式最終的。 其他方法相同。

也就是說,如果子類決定擴展您的類並破壞其不變式,那實際上不是您的問題。 這是子類和該子類的客戶的問題,他們選擇使用此損壞的子類。

但是要記住的是,如果您的基類是為擴展而設計的(本質上不是一件好事),那么您的業務就是確保在推出新版本的基類時這些擴展仍然有效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM