【问题标题】:Why use a superclass reference for an object? [duplicate]为什么要对对象使用超类引用? [复制]
【发布时间】:2014-02-11 17:10:57
【问题描述】:

给定以下代码:

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

【问题讨论】:

  • 另一个好处是,如果你想把所有的音乐家放到一个数据结构中(比如一个列表),你可以做到,没有超类你做不到。
  • stackoverflow.com/questions/383947/… 相关。不知道我是否会称它为重复。
  • 您的问题标题不正确。它应该类似于“为什么要为对象使用超类引用”
  • 在像这样的孤立的 sn-ps 中,多态性的例子通常看起来过于成熟和多余。你需要看到一个更大的、设计良好的代码库来理解这些微妙之处。
  • @BackSlash,我不明白你,因为如果我有一个 List list = new ArrayList(),我可以放吉他手或鼓手,即使我已经创建了两者作为子类。

标签: java oop polymorphism


【解决方案1】:

当您可以在任何 Musician 上调用play() 时,多态的优势就出现了,无论是GuitaristDrummer,还是您认为Musician 的任何其他子类可能还没有创建。

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

这可能会输出类似

Guitarist strumming
Drummer drumming

如果您在两个子类中都覆盖了play() 方法。这样一来,调用play() 的代码就不需要知道Musician 的具体实现是什么,只要知道它是一个Musician 就可以保证有一个play() 方法。

能够从超类引用中调用strummingStrings等子类方法并不是多态的优势,因为该方法只存在于子类中。它不保证存在于超类中。如果需要调用strummingString 等纯子类方法,则需要子类引用。

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

【讨论】:

    【解决方案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”类由Musicians 的集合组成。乐队类通过调用 play 方法告诉他们何时开始演奏。实现细节留给子类型,这是继承的力量实现的地方。

    【讨论】:

      【解决方案3】:

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

      在你的例子中:

       m1.strummingStrings(); //COMPILATION ERROR!
      

      如果您正在为接受Musician 的类编写测试驱动程序,那就太好了。

      当然,在许多方面,在函数的开始和函数结束之间,编码标准并不那么重要,因为读者和维护者可以全面了解正在发生的事情。所以在示例中:

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

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

      很少会产生重大影响。

      但是

      Musician foo() {
      // ...
      }
      

      Guitarist foo() {
      // ...
      }
      

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

      【讨论】:

        【解决方案4】:

        GuitaristDrummer 被称为 specializationsMusician。他们执行特定的技巧,并且他们有特定的演奏方式。如果你有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();
                }
            }
        }
        

        现在,您需要在 DrummerMusician 中覆盖 play 以提供特殊行为:

        public class Drummer extends Musician {
            @Override
            public void play() {
                this.turnsDrumStick();
            }
            [...]
        }
        

        【讨论】:

          【解决方案5】:

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

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

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

          这些技术有助于保持代码的逻辑组织性和灵活性,并允许实例化类在运行时不知道子类的类型,这对代码的灵活性有很大好处。

          【讨论】:

          • 您能指出一本书或一篇文章,其中可以看到更多此类技术吗?
          猜你喜欢
          • 1970-01-01
          • 2018-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-28
          • 1970-01-01
          相关资源
          最近更新 更多