继承是否违反了 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 PrincipleSuper 的所有属性也必须适用于 Sub。这意味着 Super 的所有私有成员(方法和属性)都存在于 Sub 的实例中。只是出于组织原因,Super 的开发人员决定他们不希望任何派生的 类 直接弄乱它。

现在,这与内存分配有什么关系?在 Java 的情况下,构造函数不分配内存是正确的。它只是初始化字段。内存分配由运行时处理,它为整个画面分配足够的空间。请记住,SubSuper ,然后是 。所以它分配了足够的内存来保存从整个继承链一直到 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问题.