确保子类的正确性?

时间:2013-11-07 05:40:57

标签: java inheritance override subclass method-overriding

如果我们在子类中启用覆盖,如何确保结构的正确性?

或者,换句话说,我们必须确保这一点吗? (如果不是,请给出有力的理由。)


以下是一个显示问题的简单示例:

我们的结构: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]
    }
}

使用后,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]
    }
}

正如我们所看到的,即使南希明确地回到父级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()。)


你有什么看法?请提供一些解决方案。非常感谢您的投入!!

1 个答案:

答案 0 :(得分:0)

首先,如果你不希望你的类被子类化,或者没有设计它以便被子类化,你可以(并且你应该)使它成为最终的,或者至少记录该类的事实可扩展仅供内部使用,不得延长。

现在,如果你的目标是将这个类作为子类,你确实必须设计它,以便子类不会轻易破坏超类的不变量。这是一项艰苦的工作。

在您的示例中,没有理由允许重新定义isEven(),因此您应该将其设为最终(如果您想将其公开),或私有(如果您不想制作)它公开)。私有方法是隐含的最终决定。与其他方法相同。

那就是说,如果一个子类决定扩展你的类并打破它的不变量,那不是你的问题。这是子类和该子类的客户端的问题,它们选择使用这个破坏的子类。

要记住的是,如果您的基类是为扩展而设计的(这本身并不是一件好事),那么您的业务是确保在推出新版本时这些扩展仍然有用你的基类。