为什么要为对象使用超类引用?

时间:2014-02-11 17:10:58

标签: java oop polymorphism

给出以下代码:

public class Musician {

    public void play() {
        // do something
    }
}

public class Drummer extends Musician {

    public void turnsDrumStick() {
        // do something
    }
}

public class Guitarist extends Musician {

    public void strummingStrings() {
       // do something
    }
}

我可以使用多态来执行以下操作:

    Musician m1 = new Guitarist();
    Musician m2 = new Drummer();

    m1 = m2;

但是,我看不到子类的方法:

    m1.strummingStrings(); //COMPILATION ERROR!

如果我使用:

Guitarist m1 = new Guitarist();

不是更好吗?使用Musico类型引用子类的对象有什么好处?例如,我可以将m1 = m2;归为一种可能性吗?或者还有其他优势吗?

我看过这篇文章,但我仍然感到困惑:Using superclass to initialise a subclass object java

5 个答案:

答案 0 :(得分:5)

当您可以在任何 play()上调用Musician时,无论是GuitaristDrummer还是任何<{1}},都可以获得多态性的优势您可能尚未创建的Musician的其他子类。

Musician m1 = new Guitarist();
Musician m2 = new Drummer();
m1.play();
m2.play();

这可能会输出类似

的内容
Guitarist strumming
Drummer drumming

如果覆盖两个子类中的play()方法。这样,调用play()的代码不需要知道它实际上是Musician的哪个实现,只是它是Musician并且保证有play()方法

多态性不能从超类引用中调用诸如strummingStrings的子类方法,因为该方法仅存在于子类中。它不能保证存在于超类中。如果需要调用仅类的子类方法,例如strummingString,则需要子类引用。

可以在超类strummingStrings中定义Musician,并且多态可以工作,但这将是一个糟糕的设计。并非所有的音乐家都可以在吉他上弹奏弦乐。

答案 1 :(得分:2)

很好的问题,但您遇到的问题是您的设计所固有的。

如果您这样做:

public class Musician {

    public void play() {
        // do something
    }
}
.

public class Drummer extends Musician {

    public void turnsDrumStick() {
        // do something
    }

    public void play() { 
        //...perhaps some other things....
        turnsDrumStick();
    }
}
.

public class Guitarist extends Musician {

    public void strummingStrings() {
       // do something
    }

    public void play() {
       strummingStrings();
       //other things
    }
}

所以“Band”类由Musician s的集合组成。乐队类通过调用play方法告诉他们何时开始播放。实现细节留给子类型,这是实现继承的力量的地方。

答案 2 :(得分:0)

如果您正在编写实际需要并使用Musician的方法,则不会意外地依赖于子类功能。

在你的例子中:

 m1.strummingStrings(); //COMPILATION ERROR!

如果您正在为接受Musician的课程编写测试驱动程序,那么这是一件好事。

当然,在函数的开头和函数的结尾之间,编码标准在很多方面并不重要,因为读者和维护者可以全面了解正在发生的事情。所以在这个例子中:

void foo() {
     Guitarist g;
     g.play();
}

void foo() {
     Musician m;
     m.play();
}

很少会有很大的不同。

但是

Musician foo() {
// ...
}

Guitarist foo() {
// ...
}

会有很大的不同。在前一种情况下,客户端代码永远不会被期望一个吉他手,而在后一个代码中,实现者永远与永远返回的吉他手耦合,即使将来它发现Banjoist可以更容易实现和返回,因为客户端代码取决于它。

答案 3 :(得分:0)

GuitaristDrummer被称为specializations的{​​{1}}。他们执行特定的技巧,他们有特定的游戏方式。如果你有一个Musician,你可能会有类似的东西:

Band

现在,您需要覆盖public class Band { private List<Musician> musicians = new ArrayList<Musician>(); public void addMusician(Musician m) { musicians.add(m); } public void play() { for (Musician m : musicians) { m.play(); } } } play中的Drummer以提供特殊行为:

Musician

答案 4 :(得分:0)

他们你编写类的方式,没有理由将子类化为子类,因为这两个子类实际上都没有使用音乐家的play()方法。将m1和m2声明为音乐家而不是它们各自的子类的优点是实例化类不需要知道play()方法是如何工作的;它可以调用m1.play()和m2.play(),子类知道该怎么做。

要从这种灵活性中受益,您需要重新构建对象。我建议,至少要制作Musician摘要,然后将子类中的play()方法覆盖为strum或drum。

如果你想更进一步,你可以定义一个Play接口,并为名为StrumGuitar和Drum的类实现该接口,从而封装这些行为。然后,例如,吉他手的play()方法可以将它的行为委托给StrumGuitar类。此外,实例化代码只需调用play()而不必担心它的作用。

这些技术有助于保持代码在逻辑上的组织性,灵活性,并允许在运行时实例化类不知道子类的类型,这可以大大提高代码的灵活性。