如果我们在子类中启用覆盖,如何确保结构的正确性?
或者,换句话说,我们必须确保这一点吗? (如果不是,请给出有力的理由。)
以下是一个显示问题的简单示例:
我们的结构: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()
。)
答案 0 :(得分:0)
首先,如果你不希望你的类被子类化,或者没有设计它以便被子类化,你可以(并且你应该)使它成为最终的,或者至少记录该类的事实可扩展仅供内部使用,不得延长。
现在,如果你的目标是将这个类作为子类,你确实必须设计它,以便子类不会轻易破坏超类的不变量。这是一项艰苦的工作。
在您的示例中,没有理由允许重新定义isEven()
,因此您应该将其设为最终(如果您想将其公开),或私有(如果您不想制作)它公开)。私有方法是隐含的最终决定。与其他方法相同。
那就是说,如果一个子类决定扩展你的类并打破它的不变量,那不是你的问题。这是子类和该子类的客户端的问题,它们选择使用这个破坏的子类。
要记住的是,如果您的基类是为扩展而设计的(这本身并不是一件好事),那么您的业务是确保在推出新版本时这些扩展仍然有用你的基类。