0

サブクラスでオーバーライドを有効にした場合、構造の正確さを保証するにはどうすればよいですか?

または、言い換えれば、これを保証する必要がありますか? (いいえの場合は、明確な理由を教えてください。)


問題を示す簡単な例を次に示します。

私たちの構造:偶数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); }
}

ただし、悪のボブは、この (間違った) サブクラスでそれをオーバーライドします。

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]
    }
}

使用すると、ナンシーはいくつかの追加機能が必要になるため、ボブの (間違った) サブクラスを選択します。

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 が明示的に parent-class にキャストし直してもEvenArrayList、問題は解決しません。


これまでのところ、これを回避するための解決策を考えています (アリスがライブラリを開発している間)。ただし、これは良い解決策ではありません。

(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); }

    // ...
}

3 つのアプローチはほぼ同じです。

(1) は、同じコード ブロックを再利用できないため、あまり良くありません。

(2) と (3) は、メソッドの数がほぼ 2 倍になり、クラスが複雑になるため、あまり良くありません。さらに、オーバーライド可能なメソッドを誤って使用する可能性があるという「トラップ」が作成されます。

さらに、(2) と (3) では、関数呼び出しのオーバーヘッドが少し発生します。


この問題は両方の可能性があります

(1) セキュリティ上の問題:

ボブが意図的にいくつかのメソッドを誤ってオーバーライドし、他の人に彼の追加された (悪) 機能ライブラリを使用するように誘惑し、ナンシーがアリスのバージョンのメソッドを使用していると思っていても、キャスティングによってハッキングされた場合。

(2) ライブラリ開発者にとっての潜在的な罠:

Alice と Bob の両方の役割を果たしたとしても、サブクラスでの不適切なオーバーライドにより構造全体 (ArrayList でさえも) が壊れる可能性があり、エラーが発生している場所が原因から遠く離れているため、エラーの原因が簡単に見つからない可能性があります (決してご利用の際はお電話isEven()ください)。


あなたの意見は何ですか?いくつかの解決策を提供してください。たくさんのご応募ありがとうございました!!

4

1 に答える 1

0

まず第一に、クラスをサブクラス化したくない場合、またはサブクラス化するためにクラスを設計していない場合は、クラスを最終的なものにすることができます (そうするべきです)。内部目的のみであり、拡張すべきではありません。

このクラスをサブクラス化することが目標である場合、サブクラスがスーパークラスの不変条件を簡単に破らないように設計する必要があります。それは大変な運動です。

あなたの例では、の再定義を許可する理由はないisEven()ので、単純に final (公開したい場合) または private (公開したくない場合) にする必要があります。プライベート メソッドは暗黙的に final です。他の方法についても同様です。

とはいえ、サブクラスがクラスを拡張してその不変条件を破ることを決定した場合、それは実際には問題ではありません。これは、この壊れたサブクラスを使用することを選択したサブクラスとこのサブクラスのクライアントの問題です。

ただし、覚えておくべきことは、基本クラスが拡張用に設計されている場合 (これは本質的には良いことではありません)、基本クラスの新しいバージョンをロールアウトするときに、これらの拡張機能が引き続き機能することを確認することです。 .

于 2013-11-07T07:37:45.480 に答える