【问题标题】:Using superclass type for subclass instance对子类实例使用超类类型
【发布时间】:2013-10-01 10:01:02
【问题描述】:

我知道这个问题被问了很多,但在我看来,通常的答案远不能令人满意。

给定以下类层次结构:

class SuperClass{}
class SubClass extends SuperClass{}

为什么人们使用这种模式来实例化子类:

SuperClass instance = new SubClass();

而不是这个:

SubClass instance = new SubClass();

现在,我看到的通常答案是,这是为了将 instance 作为参数发送给需要 SuperClass 实例的方法,如下所示:

void aFunction(SuperClass param){}

//somewhere else in the code...
...
aFunction(instance);
...

但是我可以将 SubClass 的实例发送到 aFunction 而不管保存它的变量的类型!这意味着以下代码将编译并运行而不会出现错误(假设之前提供的 aFunction 定义):

SubClass instance = new SubClass();
aFunction(instance);

事实上,AFAIK 变量类型在运行时是没有意义的。它们仅供编译器使用!

将变量定义为 SuperClass 的另一个可能原因是,如果它有几个不同的子类,并且该变量应该在运行时切换它对其中几个的引用,但例如我只在类中看到了这种情况(不是超级,不是子。只是类)。绝对不足以要求通用模式...

【问题讨论】:

  • 因为他们不想知道实例是什么类型的子类。他们对超类的上下文感兴趣。
  • 换句话说,你需要什么 ATM 是主观的,而不是最佳实践。对吗?

标签: java inheritance casting polymorphism type-conversion


【解决方案1】:

这种编码的主要论点是因为Liskov Substituion Principle,它指出如果XT 类型的子类型,那么T 的任何实例都应该能够被替换为X

这样做的好处很简单。假设我们有一个具有属性文件的程序,如下所示:

mode="Run"

你的程序看起来像这样:

public void Program
{
    public Mode mode;

    public static void main(String[] args)
    {
        mode = Config.getMode();
        mode.run();
    }
}

简单地说,这个程序将使用配置文件来定义这个程序启动的模式。在Config 类中,getMode() 可能如下所示:

public Mode getMode()
{
    String type = getProperty("mode"); // Now equals "Run" in our example.

    switch(type)
    {
       case "Run": return new RunMode();
       case "Halt": return new HaltMode();  
    }
}

为什么这不起作用

现在,因为您有一个Mode 类型的引用,您可以通过简单地更改mode 属性的值来完全更改程序的功能。如果您有public RunMode mode,您将无法使用此类功能。

为什么这是一件好事

这种模式非常流行,因为它为程序打开了可扩展性。这意味着如果作者希望实现这种功能,这种类型的理想功能可以通过最少的更改来实现。我的意思是,来吧。您更改配置文件中的一个单词并完全改变程序流程,而无需编辑任何一行代码。这是可取的。

【讨论】:

  • 这确实是一个很好的答案,但这里的优势取决于这个特定的设计(例如,将引导模式实现为与 HashMap 相对的自定义对象)但我可以看到模块化/可扩展应用程序如何获利这种做法。感谢您的回答。
  • 另一件事,这(像这里的大多数其他答案一样)处理参数而不是变量。所以基本上它没有回答我的问题。但我想真正的答案是局部变量不会从中获得任何东西(除非它们旨在保存参数的值)并且字段显然与 getter 和 setter 中的公共方法有很深的联系,所以无论在哪里参数可以从超类中获得,字段也必须是超类才能获得。
  • 在我看来,我认为更多的是遵循这些实践,因此它为您将来打开这些大门。如果您深入研究应用程序,而您的老板转身说“如果我在这里输入不同的单词,我希望程序完全改变”,并且您一直遵循这些原则,那么您已经奠定了所有它的基础。
【解决方案2】:

在许多情况下,这并不重要,但被认为是好的风格。 您将提供给参考用户的信息限制为必要的信息,即它是SuperClass 类型的实例。变量是否引用 SuperClassSubClass 类型的对象并不(也不应该)重要。

更新

对于从未用作参数等的局部变量也是如此。 正如我所说,这通常无关紧要,但被认为是好的样式,因为您可能稍后更改变量以保存参数或超类型的另一个子类型。在这种情况下,如果您首先使用子类型,则您的进一步代码(在该单一范围内,例如方法)可能意外地依赖于一个特定子类型的 API 并更改变量以保存另一种类型可能会破坏您的代码。

我将扩展 Chris 的示例:

考虑你有以下几点:

RunMode mode = new RunMode();

...

您现在可能会依赖 modeRunMode 这一事实。

但是,稍后您可能希望将该行更改为:

RunMode mode = Config.getMode(); //breaks

糟糕,无法编译。好的,让我们改变它。

Mode mode = Config.getMode(); 

该行现在可以编译,但您的进一步代码可能会中断,因为您不小心将mode 视为RunMode 的一个实例。请注意,它可能会编译,但可能会在运行时中断或破坏您的逻辑。

【讨论】:

  • 很好的答案,但我没有询问方法签名。我询问了使用我在我的 OP 中描述的特定设计...
  • @YekhezkelYovel 我也没有谈论方法签名;)在方法中,也有不需要/不应该知道确切类型的局部变量用户。
  • 您确实做到了:) 'Config.getMode()' 不是实例化,而是静态函数调用。它返回在其签名中声明的类型的实例,因此您谈论签名。不过,你没有谈论方法,我的错。 ;)
  • 在一个方法中,不知道类型只有在返回值时才有意义——如果它是在本地实例化的则没有意义。这就是为什么您谈论函数调用而不是实例化的原因。这就是为什么我发表了令人讨厌的评论:)
  • @YekhezkelYovel 如果您谈论实例化,那么是的,您不需要将新实例分配给超类型的变量。但是您的代码通常不仅仅涉及实例化,并且可能在以后更改(这是我更新的重点),这就是为什么使用超类型被认为是好的风格。顺便说一句,您实际上不是在谈论实例化,而是在谈论 assignment ;)
【解决方案3】:

SuperClass instance = new SubClass1()

几行之后,你可以做instance = new SubClass2();

但如果你写,SubClass1 instance = new SubClass1();

几行之后,你不能做instance = new SubClass2()

【讨论】:

  • 仔细阅读我的帖子,我提到了这个答案,并说为什么它没有让我满意。
【解决方案4】:

称为多态,是对子类对象的超类引用。

In fact, AFAIK variable types are meaningless at runtime. They are used 
only by the compiler!

不知道你是从哪里读到的。在编译时,编译器只知道引用类型的类(如您所说,在多态的情况下是超类)。在运行时,java 知道 Object(.getClass()) 的实际类型。在编译时,java 编译器只检查调用的方法定义是否在引用类型的类中。调用哪个方法(函数重载)在运行时根据对象的实际类型确定。

Why polymorphism?

好吧,谷歌可以找到更多,但这里是一个例子。你有一个常用的方法draw(Shape s)。现在形状可以是RectangleCircle 任何CustomShape。如果您不在draw() 方法中使用形状引用,您将不得不为每种形状类型(子类)创建不同的方法。

【讨论】:

  • 感谢有关多态性的课程,但是如果您查看我给出的标签,您会发现其中之一是“多态性”。我知道多态性,我的论点是我提到的这种编码实践与多态性无关。如果您想知道我为什么这么认为,请参阅我的 OP(再次)...
  • 我猜你是在我发布答案后添加了最后一段,否则我错过了。无论如何,重点是它完全是设计特定的。现在现实软件世界中发生的事情是软件不是一次创建的。这是发布和升级的周期。您已经在一个类中看到了这种模式(没有子,超级),可能是因为它是一种一般预防措施(或某种意义上的设计约定),假设代码将来会被修改,并且有可能继承稍后进入图片。
  • 也回答你的问题换句话说,这取决于你需要什么 ATM,而不是最佳实践。对吗? 是的,你需要什么是主观的 ATM 是你的设计所要求的(你很确定这方面的代码将来不会被修改)。但一般惯例是使用多态引用。
  • 如您所见,我从未编辑过 OP,所以您一定错过了。关于真实的软件世界,我想这取决于你创建的应用程序的类型,是桌面应用程序、移动应用程序、服务器应用程序、它们构建在什么平台上等等。也许你一直在使用这种做法,而其他人可能不是。我个人问了这个问题,因为我在 SO 上看到了很多这种做法,但没有在工作中看到,这让我想知道为什么使用它的人会这样做。
  • 另外,你也把它带到了方法签名中,这不是我的问题(我也使用多态参数,甚至接口类型参数,但我的问题是关于声明一个变量,而不是一个参数)
【解决方案5】:

这是从设计的角度来看,您将拥有一个超类,并且可以有多个子类来扩展功能。

必须编写子类的实现者只需关注要覆盖哪些方法

【讨论】:

  • 好的,我想这里的几乎每个人都应该看到这个答案并跳过他们自己的答案,因为他们中的大多数人基本上都说了同样的话。同样,我在帖子中提到了这个答案,并解释了为什么它对我来说不够好。基本上,这取决于您的代码的目标。如果您不编写可扩展的应用程序,这将变得不必要且繁重。你确实说过“这是从设计的角度来看的”,所以基本上我同意你的看法。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-23
  • 2021-10-10
  • 2016-06-13
  • 1970-01-01
  • 2015-10-07
  • 2010-10-30
  • 2015-05-29
相关资源
最近更新 更多