在 OOP 主题的示例中,我经常注意到基于某个类创建的对象存储在“父类”类型的变量中。让我用一个例子来解释:
class Pet {
// реализация
}
class Cat extends Pet {
// реализация
}
然后,在 main 方法中:
Pet myCat = new Cat();
“根据经典”,myCat 变量应该被声明为 Cat 类型,这是合乎逻辑的。我注意到通过这种方式可以将不同类型的对象分组到集合中,但是具有相同的祖先,但是,这些对象的所有功能都将仅限于这个祖先。
我想知道为什么要这样做,以及你能从中得到什么或失去什么。
你可以举一个基于Java的例子。您在某些方法中声明了一个集合对象,但您使用 List 接口定义它
您的对象将使用 List 接口具有的方法,而您可以将对象强制转换为 ArrayList 实现并使用 ArrayList 方法
但是如果你不这样做,而是严格使用 List 接口中描述的方法,你可以随时更改 ArrayList 的实现,例如改为 LinkedList,为此你只需修复代码中的一行,在变量的定义中,其余代码不必更改。
因此,例如,ArrayList 允许您非常快速地访问元素 - 复杂度 O (1),而用于访问元素的 LinkedList 具有复杂度 O (n)。同时,ArrayList 需要更长的时间将元素插入到集合的中间,而对于 LinkedList 来说这是一个非常快的操作。当您开始编写代码时,您可以使用一些泛型类来描述您的变量,然后您可以快速转向该类的更有益的实现。
您提供的示例
以这种形式,它仅在“引用”编程语言中才有意义,其中
myCat它实际上不是类型变量Pet(正如您错误地假设的那样),而是可以引用类的子层次结构中任何类型的对象的引用根Pet。这样的链接将表现出多态性,即 尽管它在外部具有 typePet,但它的行为就像Cat(在公共接口的框架内)。实际上,您提供的示例旨在演示的正是这种多态性。
在 C++ 中,必须显式声明类型“引用”,您的原始示例没有意义。在 C++ 中,您必须执行相同的操作,例如通过指针
这里可以清楚的看到
Pet左边其实没有类型变量。只有一个指向 的指针(或某种其他形式的引用)Pet实际上指向Cat。该问题说明了 OOP 中“多态性”原则的典型实现。
要理解这个原理,有必要了解几个重要的点:
在经典的 OOP 中,对象是基于某种类型(类)创建的,并且在该对象的生命周期中,创建它所基于的类型不能更改。例外情况是某些编程语言可以修改已创建的对象 - 可以向该对象添加新方法和其他扩展,但这在所讨论问题的上下文中并不重要。对象的类型在其生命周期内不会改变这一事实的后果之一是,将完全使用创建该对象的类的方法和属性的整个实现。不管这个对象的这个或那个方法是如何被调用的。因此,如果我们在给定对象上调用方法,我们总是会得到给定对象所基于的类型的行为。来自 TS 问题 - 变量方法
myCatCat即使变量的类型是.也会执行类的行为Pet。变量类型决定了可用于该变量的方法集。例外的是一些对象类型允许你通过名字来调用对象的方法,例如
object很多编程语言中的一个类型可以用来调用对象的任何方法而无需键入它。通过这样的调用,将执行后期绑定,如果这样的对象有被调用的方法,那么它将被执行,如果它不存在,则会产生运行时错误!不是编译时错误!。将对该基类的任何后代的对象的引用放入基类类型变量中是完全安全的。这在 TC question 的代码行中做得非常正确
Pet myCat = new Cat();,这不会改变对象类型,但基类变量本身的类型将限制由于该变量而可以调用的方法集。例如,如果在类Cat中定义了公共方法CatMyau,则从变量调用此方法myCat将不起作用,因为该方法在类中不存在Pet。在创建了基类对象的集合后,我们可以处理该类的任何子对象。例如,我们可以创建一个包含宠物的集合,
Pet我们可以为一个类定义一个抽象的公共方法Voice,并为基类的所有后代实现该方法。例如,Cat它会喵喵叫,狗会吠叫。当遍历宠物时,我们可以调用变量 type 上的Pet方法Voice,并且根据放置在我们集合中的类对象,我们将听到一只或另一只动物的声音。猫会喵喵叫,狗会叫。