0

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

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


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

我们的结构: anArrayList只包含偶数

库开发人员 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 明确地转换回 parent-class 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) 或者,仅使用 final 方法(因为它们不能被覆盖):

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 的方法版本,Nancy 仍然通过强制转换被黑客入侵。

(2) 和库开发人员的潜在陷阱:

即使我们同时扮演 Alice 和 Bob,子类中不正确的覆盖可能会破坏整个结构(甚至是 ArrayList),并且可能不容易找到错误的原因,因为它发生的位置与原因相去甚远(我们从来没有调用isEven()使用)。


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

4

1 回答 1

0

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

现在,如果您的目标是让这个类成为子类,那么您确实必须对其进行设计,以便子类不会轻易破坏超类的不变量。这是一项艰苦的运动。

在您的示例中,没有理由允许重新定义isEven(),因此您应该简单地将其设为 final (如果您想公开)或私有(如果您不想公开)。私有方法是隐含的最终方法。其他方法也一样。

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

但是要记住的是,如果您的基类是为扩展而设计的(这本质上不是一件好事),那么您的工作就是确保在推出新版本的基类时这些扩展仍然有效.

于 2013-11-07T07:37:45.480 回答