继承是否违反了oops的基本规律..?

时间:2015-03-29 04:45:50

标签: java oop inheritance

首先让我解释一下我想说的话

假设一个Sub类继承了Super类。 现在我们可以在Sub类中超出所有类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类对象也会调用Super类构造函数。 但是构造函数的工作只是初始化字段 - 而不是将内存分配给对象。

此外,在不允许初始化的抽象类的情况下,我们仍然可以使用抽象类的实例变量。

仅在实例创建时分配内存到实例变量。

我们如何在不创建实例的情况下使用实例字段。 它不会违反oops概念.. ??

请帮忙解决这个问题。并提前感谢。

3 个答案:

答案 0 :(得分:4)

我认为您对使用extends关键字时会发生什么感到困惑。该关键字的含义是Sub是一种更具体的 Super。根据{{​​3}},Super的所有属性也必须包含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方法仍将使用{{1}}中的方法。这可能有点令人困惑,所以最好的建议是不要那么想。通常,您需要覆盖方法而不是字段。

答案 1 :(得分:1)

我完全赞同伊恩的回答。完全。关于你问题的标题,

  

继承是否违反了oops的基本规律..?

答案取决于。有一种违反封装原则的继承:实现继承

每次从未标记为extends的类继承(通过abstract原语)时,您都会使用实现继承。在这种情况下,要知道如何实现子类,您需要知道基类方法的实现(a.k.a.代码)。覆盖方法时,必须确切知道基类中该方法的行为。这种代码重用通常称为白盒重用

引用GoF的书“设计模式:

  

父类通常至少定义其子类的一部分'物理代表。因为继承将子类暴露给其父实现的细节,所以它经常说"继承打破了封装"。

因此,为了减少实现依赖性,您必须遵循可重用的面向对象设计的原则之一,即:

编程到接口,而不是实现

答案 2 :(得分:0)

继承只关心完成什么和如何完成,而不关心所承诺的。如果您违反了基层的承诺,将会发生什么?有什么保证可以确保它兼容吗? -即使您的编译器也不会理解此错误,并且您将面临代码中的错误。如:

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 {
        // ...
}

如果该类想使用继承以达到代码重用的目的,则它可以继承违反其主体的行为,例如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;
    }
}

我只是用HashSet类重新实现了DataHashSet以便跟踪插入。实际上,DataHashSet继承并且是HashSet的子类型。我们可以通过HashSet代替DataHashSet(在Java中是可能的)。另外,我确实重写了基类的某些方法。根据李斯科夫替代原则,这合法吗?由于我没有对基类的行为进行任何更改,因此只需添加一条插入动作的轨道即可,这似乎是完全合法的。但是,我认为这显然是危险的继承和错误代码。首先,我们应该了解add方法的确切作用。将一个单位添加到相关属性并调用父类方法。名为yo-yo的问题。看一下addAll方法,首先,它将集合大小添加到相关属性中,然后在父级中调用addAll,但是父级addAll到底能做什么?它将多次调用add方法(遍历集合),哪个add将被调用?当前类中的add,因此count的大小将被添加两次。一次调用addAll,第二次调用父类将在子类中调用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将被调用?第二个将调用类B中的bar。但是,这是什么问题呢?问题是类A中的foo方法对类B中的bar方法的覆盖一无所知,那么您的不变式可能会受到侵犯。因为foo可能期望bar方法的唯一行为是在自己的类中,所以不会覆盖某些内容。此问题称为脆弱的基类问题