Why use a superclass reference for an object?
给定以下代码:
1 2 3 4 5 6 | public class Musician { public void play() { // do something } } |
.
1 2 3 4 5 6 | public class Drummer extends Musician { public void turnsDrumStick() { // do something } } |
.
1 2 3 4 5 6 | public class Guitarist extends Musician { public void strummingStrings() { // do something } } |
我可以使用多态性来执行以下操作:
1 2 3 4 | Musician m1 = new Guitarist(); Musician m2 = new Drummer(); m1 = m2; |
但是,我看不到子类的方法:
1 | m1.strummingStrings(); //COMPILATION ERROR! |
如果我使用:
1 | Guitarist m1 = new Guitarist(); |
不是更好吗?使用musico类型引用子类的对象有什么好处?比如说,我可以将
我看到了这个帖子,但是我还是很困惑:使用超类来初始化子类对象Java
多态性的优势在于,你可以在任何
1 2 3 4 | Musician m1 = new Guitarist(); Musician m2 = new Drummer(); m1.play(); m2.play(); |
这可能会输出
1 2 | Guitarist strumming Drummer drumming |
如果您在两个子类中重写
能够从超类引用调用子类方法(如
你可以在超类
很好的问题,但是你所面临的问题是你设计的固有问题。
如果你这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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"类由
按照编写类的方式,实际上没有理由对音乐家进行子类化,因为这两个子类实际上都不使用音乐家play()方法。将m1和m2声明为音乐家而不是其各自的子类的好处是,实例化类不需要知道play()方法的工作原理;它只需要调用m1.play()和m2.play(),子类就知道该做什么。
为了从这种灵活性中获益,您需要重新构造对象。我建议,至少,将音乐家抽象化,然后将子类中的play()方法重写为strum或drum。
如果您想更进一步,可以定义一个播放接口,并为称为strumguitar和drum的类实现该接口,从而封装这些行为。然后,例如,吉他手的play()方法可以将其行为委托给strumguitar类。此外,实例化代码只需调用play(),而不必担心它会做什么。
这些技术有助于保持代码的逻辑组织性、灵活性,并允许实例化类在运行时不知道子类的类型,这对代码的灵活性有很大的好处。
1 2 3 4 5 6 7 8 9 10 11 | 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(); } } } |
现在,您需要覆盖
1 2 3 4 5 6 7 | public class Drummer extends Musician { @Override public void play() { this.turnsDrumStick(); } [...] } |
如果您编写的方法实际需要并使用
在您的示例中:
1 | m1.strummingStrings(); //COMPILATION ERROR! |
如果你正在为一个接受
当然,在函数的开始和结束之间,编码标准在很多方面都没有那么重要,因为读者和维护人员可以完全理解正在发生的事情。所以在这个例子中:
1 2 3 4 | void foo() { Guitarist g; g.play(); } |
和
1 2 3 4 | void foo() { Musician m; m.play(); } |
很少会有什么大的不同。
但是
1 2 3 | Musician foo() { // ... } |
和
1 2 3 | Guitarist foo() { // ... } |
会有很大的不同。在前一种情况下,永远不允许客户机代码期望一个吉他手,而在后一种情况下,实现者永远与永远返回的吉他手耦合在一起,即使在将来,一个Banjoist可以更容易地实现和返回,因为客户机代码依赖于它。