继承是否违反了 oops.. 的基本法则?
does inheritance violates the basic law of oops..?
我先解释一下其实我想说的
假设一个 class Sub 继承了 class Super。
现在我们可以在 class Sub 中超出 class Super 的所有非私有成员。现在假设情况
class Super{
private int id;
public int getId()
{
return id;
}
}
class Sub extends Super {
public static void main(String args[]){
Sub sub = new Sub();
System.out.println(sub.getId());
}
}
我知道创建 Sub class 对象也会调用 Super class 构造函数。
但是构造函数的工作只是初始化字段——而不是为对象分配内存。
此外,在不允许初始化的abstract class情况下,我们仍然可以使用abstract class的实例变量。
实例变量的内存将仅在创建实例时分配。
我们如何在不创建实例的情况下使用实例字段。
难道这不是 oops 概念的紫罗兰色吗??
请帮忙解决这个问题。提前致谢。
我认为您对使用 extends
关键字时发生的情况感到困惑。该关键字的意思是 Sub
是更具体的 种 Super
。根据 Liskov Substitution Principle,Super
的所有属性也必须适用于 Sub
。这意味着 Super
的所有私有成员(方法和属性)都存在于 Sub
的实例中。只是出于组织原因,Super
的开发人员决定他们不希望任何派生的 类 直接弄乱它。
现在,这与内存分配有什么关系?在 Java 的情况下,构造函数不分配内存是正确的。它只是初始化字段。内存分配由运行时处理,它为整个画面分配足够的空间。请记住,Sub
是 Super
,然后是 。所以它分配了足够的内存来保存从整个继承链一直到 java.lang.Object
.
的所有内容
abstract
类 实际上可以被初始化,甚至强制他们的派生 类 初始化他们的成员。例如:
public abstract class Super {
private int id;
public Super(int id) {
this.id = id;
}
public int getId() { return this.id; }
}
public class Sub extends Super {
public Sub() {
super(5); // failure to call this constructor is a compiler error
}
}
现在,因为 Sub
看不到 Super
的私有 id
字段,它可以自由地声明一个新的自己的字段。此 不会 覆盖 Super
的字段。 Super
的任何使用该字段的方法仍将使用 Super
中的方法。这可能有点令人困惑,所以最好的建议是不要那样想。通常,您需要覆盖 方法 而不是字段。
我完全同意伊恩的回答。完全。关于你的问题标题,
Does inheritance violates the basic law of oops..?
答案是这取决于。有一种继承违反了封装原则:实现继承.
每次从未标记为 abstract
的 class 继承(通过 extends
原语)时,您都在使用实现继承。在那种情况下,要知道如何实现你的子class,你需要知道基class的方法的实现(a.k.a。代码)。当您重写一个方法时,您必须确切地知道该方法在基 class 中的行为是什么。这种代码重用通常被称为white-box重用.
引用 GoF 的书,设计模式:
Parent classes often define at least part of their subclasses' physical representation. Because inheritance exposes a subclass to details of its parent's implementation, it's often said that "inheritance breaks encapsulation".
因此,要减少实现依赖性,您必须遵循可重用 object-oriented 设计的原则之一,即:
针对接口而非实现编程
继承只关心完成什么以及如何完成,而不关心承诺什么。如果违背基地class的承诺,会发生什么?有什么保证可以确保它兼容吗? - 甚至你的编译器也不会理解这个错误,你将面临代码中的错误。如:
class DoubleEndedQueue {
void insertFront(Node node){
// ...
// insert node infornt of queue
}
void insertEnd(Node node){
// ...
// insert a node at the end of queue
}
void deleteFront(Node node){
// ...
// delete the node infront of queue
}
void deleteEnd(Node node){
// ...
// delete the node at the end of queue
}
}
class Stack extends DoubleEndedQueue {
// ...
}
如果class想要使用继承来达到代码重用的目的,它可能会继承一个违反其原则的行为,例如insertFront
。让我们再看看另一个代码示例:
public class DataHashSet extends HashSet {
private int addCount = 0;
public function DataHashSet(Collection collection) {
super(collection);
}
public function DataHashSet(int initCapacity, float loadFactor) {
super(initCapacity, loadFactor);
}
public boolean function add(Object object) {
addCount++;
return super.add(object);
}
public boolean function addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
public int function getAddCount(Object object) {
return addCount;
}
}
我只是用 DataHashSet
class 重新实现 HashSet
以便跟踪插入。事实上,DataHashSet
继承并且是HashSet
的子类型。我们可以代替 HashSet
只传递 DataHashSet
(在 java 中是可能的)。此外,我确实重写了基础 class 的一些方法。从 Liskov 替换原则来看,这是合法的吗?由于我没有对 base class 的行为进行任何更改,只是添加了一个轨道来插入操作,这似乎是完全合法的。但是,我认为这显然是一种有风险的继承和错误代码。首先,我们应该看看 add
方法到底做了什么。将一个单元添加到相关 属性 并调用父级 class 方法。 yo-yo
有问题。查看 addAll
方法,首先,它将集合大小添加到相关的 属性 然后在父级中调用 addAll
,但是父级 addAll
到底做了什么?它会多次调用 add
方法(循环遍历集合),哪个 add
会被调用?当前class中的add
,所以,count的大小会增加两次。一次当你调用 addAll 时,第二次当父 class 将调用子 class 中的 add 方法时,这就是为什么我们称之为溜溜球问题。再举一个例子,想象一下:
class A {
void foo(){
...
this.bar();
...
}
void bar(){
...
}
}
class B extends A {
//override bar
void bar(){
...
}
}
class C {
void bazz(){
B b = new B();
// which bar would be called?
B.foo();
}
}
如您在 bazz
方法中所见,将调用哪个 bar
?第二个 class B 中的 bar
将被调用。但是,这里的问题是什么?问题是 class A 中的 foo
方法对 class B 中 bar
方法的覆盖一无所知,那么你的不变量可能会被违反。因为 foo 可能期望自己 class 中的 bar 方法的唯一行为,而不是被覆盖的东西。这个问题叫做fragile base-class问题.
我先解释一下其实我想说的
假设一个 class Sub 继承了 class Super。 现在我们可以在 class Sub 中超出 class Super 的所有非私有成员。现在假设情况
class Super{
private int id;
public int getId()
{
return id;
}
}
class Sub extends Super {
public static void main(String args[]){
Sub sub = new Sub();
System.out.println(sub.getId());
}
}
我知道创建 Sub class 对象也会调用 Super class 构造函数。 但是构造函数的工作只是初始化字段——而不是为对象分配内存。
此外,在不允许初始化的abstract class情况下,我们仍然可以使用abstract class的实例变量。
实例变量的内存将仅在创建实例时分配。
我们如何在不创建实例的情况下使用实例字段。 难道这不是 oops 概念的紫罗兰色吗??
请帮忙解决这个问题。提前致谢。
我认为您对使用 extends
关键字时发生的情况感到困惑。该关键字的意思是 Sub
是更具体的 种 Super
。根据 Liskov Substitution Principle,Super
的所有属性也必须适用于 Sub
。这意味着 Super
的所有私有成员(方法和属性)都存在于 Sub
的实例中。只是出于组织原因,Super
的开发人员决定他们不希望任何派生的 类 直接弄乱它。
现在,这与内存分配有什么关系?在 Java 的情况下,构造函数不分配内存是正确的。它只是初始化字段。内存分配由运行时处理,它为整个画面分配足够的空间。请记住,Sub
是 Super
,然后是 。所以它分配了足够的内存来保存从整个继承链一直到 java.lang.Object
.
abstract
类 实际上可以被初始化,甚至强制他们的派生 类 初始化他们的成员。例如:
public abstract class Super {
private int id;
public Super(int id) {
this.id = id;
}
public int getId() { return this.id; }
}
public class Sub extends Super {
public Sub() {
super(5); // failure to call this constructor is a compiler error
}
}
现在,因为 Sub
看不到 Super
的私有 id
字段,它可以自由地声明一个新的自己的字段。此 不会 覆盖 Super
的字段。 Super
的任何使用该字段的方法仍将使用 Super
中的方法。这可能有点令人困惑,所以最好的建议是不要那样想。通常,您需要覆盖 方法 而不是字段。
我完全同意伊恩的回答。完全。关于你的问题标题,
Does inheritance violates the basic law of oops..?
答案是这取决于。有一种继承违反了封装原则:实现继承.
每次从未标记为 abstract
的 class 继承(通过 extends
原语)时,您都在使用实现继承。在那种情况下,要知道如何实现你的子class,你需要知道基class的方法的实现(a.k.a。代码)。当您重写一个方法时,您必须确切地知道该方法在基 class 中的行为是什么。这种代码重用通常被称为white-box重用.
引用 GoF 的书,设计模式:
Parent classes often define at least part of their subclasses' physical representation. Because inheritance exposes a subclass to details of its parent's implementation, it's often said that "inheritance breaks encapsulation".
因此,要减少实现依赖性,您必须遵循可重用 object-oriented 设计的原则之一,即:
针对接口而非实现编程
继承只关心完成什么以及如何完成,而不关心承诺什么。如果违背基地class的承诺,会发生什么?有什么保证可以确保它兼容吗? - 甚至你的编译器也不会理解这个错误,你将面临代码中的错误。如:
class DoubleEndedQueue {
void insertFront(Node node){
// ...
// insert node infornt of queue
}
void insertEnd(Node node){
// ...
// insert a node at the end of queue
}
void deleteFront(Node node){
// ...
// delete the node infront of queue
}
void deleteEnd(Node node){
// ...
// delete the node at the end of queue
}
}
class Stack extends DoubleEndedQueue {
// ...
}
如果class想要使用继承来达到代码重用的目的,它可能会继承一个违反其原则的行为,例如insertFront
。让我们再看看另一个代码示例:
public class DataHashSet extends HashSet {
private int addCount = 0;
public function DataHashSet(Collection collection) {
super(collection);
}
public function DataHashSet(int initCapacity, float loadFactor) {
super(initCapacity, loadFactor);
}
public boolean function add(Object object) {
addCount++;
return super.add(object);
}
public boolean function addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
public int function getAddCount(Object object) {
return addCount;
}
}
我只是用 DataHashSet
class 重新实现 HashSet
以便跟踪插入。事实上,DataHashSet
继承并且是HashSet
的子类型。我们可以代替 HashSet
只传递 DataHashSet
(在 java 中是可能的)。此外,我确实重写了基础 class 的一些方法。从 Liskov 替换原则来看,这是合法的吗?由于我没有对 base class 的行为进行任何更改,只是添加了一个轨道来插入操作,这似乎是完全合法的。但是,我认为这显然是一种有风险的继承和错误代码。首先,我们应该看看 add
方法到底做了什么。将一个单元添加到相关 属性 并调用父级 class 方法。 yo-yo
有问题。查看 addAll
方法,首先,它将集合大小添加到相关的 属性 然后在父级中调用 addAll
,但是父级 addAll
到底做了什么?它会多次调用 add
方法(循环遍历集合),哪个 add
会被调用?当前class中的add
,所以,count的大小会增加两次。一次当你调用 addAll 时,第二次当父 class 将调用子 class 中的 add 方法时,这就是为什么我们称之为溜溜球问题。再举一个例子,想象一下:
class A {
void foo(){
...
this.bar();
...
}
void bar(){
...
}
}
class B extends A {
//override bar
void bar(){
...
}
}
class C {
void bazz(){
B b = new B();
// which bar would be called?
B.foo();
}
}
如您在 bazz
方法中所见,将调用哪个 bar
?第二个 class B 中的 bar
将被调用。但是,这里的问题是什么?问题是 class A 中的 foo
方法对 class B 中 bar
方法的覆盖一无所知,那么你的不变量可能会被违反。因为 foo 可能期望自己 class 中的 bar 方法的唯一行为,而不是被覆盖的东西。这个问题叫做fragile base-class问题.