【问题标题】:Understanding Object Inheritance了解对象继承
【发布时间】:2015-05-23 16:00:34
【问题描述】:

在上一个问题(linked here) 中,有一些关于对象继承的未解决问题仍不清楚。 (我使用的术语可能不正确 - 请相应修改 - 谢谢)

简单地说,为什么超类不继承已明确分配给它的子类的属性。代码示例:

UIView *view = nil;
UILabel *label = [[UILabel alloc] init];
label.text = @"some text...";
view = label;

// view has not inherited any UILabel properties
view.text = @"new text...";              //error
view.textColor = [UIColor redColor];     //error

只是想了解一下。

谢谢。

【问题讨论】:

    标签: objective-c class object inheritance


    【解决方案1】:

    这是面向对象的概念 101。

    您写道:“简单地说,为什么超类不继承已明确分配给它的子类的属性”

    因为恰恰相反。子类继承其超类的属性。

    子类是超类的特殊版本。超类可以有多个子类。子类知道它的特殊特性以及它的超类的公共特性。但是超类对从它创建的任何子类一无所知。怎么可能?子类可能存在于几年后由完全不同的人编写的完全独立的库中。

    在您的特定示例代码中,您有一个UILabel,它是一个专门的UIViewUIViewUILabel 的任何特殊功能一无所知。

    当你尝试这样做时:

    view.text = @"some text";
    

    编译器会报错,因为它查看UIView 的定义并没有找到名为text 的属性。所以你得到一个编译错误。

    由于您在运行时碰巧知道view 实际上会指向UILabel,因此您可以使用强制转换来欺骗编译器:

    ((UILabel *)view).text = @"some text";
    

    或使用setter语法编写:

    [(UILabel *)view setText:@"some text"];
    

    通过使用强制转换,您是在告诉编译器 - “相信我,我知道它看起来像一个 UIView 变量,但我真的知道它是一个 UILabel 变量”。

    这是关于使用演员表的重要说明。你在欺骗编译器。你可能是错的。如果你错了,那么你的应用很可能会在运行时崩溃。

    【讨论】:

    • 很明显,作为编译器盲目,点符号不起作用?
    • 对不起,我不明白你的意思。请记住,点表示法只是使用实际 setter 或 getter 方法语法的简写。
    • 是的。很方便的一款。谢谢。
    【解决方案2】:

    类继承就像专业化。

    想想 Person 类。

    一个人知道如何呼吸、走路、说话、喂自己等等。(假设所有人都有一个MakeMeASandwich 方法。)

    一个人可能有专门的工作:木匠、厨师、水管工、IT 人员、卡车司机等。

    如果你有一个变量

    Person *aPerson;
    

    它是一个指向 Person 的指针。你不知道那个人做什么交易,你只知道它是一个人。您可以将 Plumber 对象分配给该变量、Carpenter 对象或任何其他专门的 Person,因为所有这些类型也属于通用类 Person。

    当您引用 aPerson 时,编译器不知道它正在与哪种类型的人交谈。

    编译器可能知道它可以告诉 Person 制作三明治,并假设所有人都知道如何做。 (所有专门的 Person 类,如 Carpenter 和 Plumber 都从其父 Person 类继承 makeMeASandwich 方法。)

    除非你告诉编译器:

    "This Person is a Carpenter" 那么如果你要求 Person 为你构建一个套牌,编译器就会抱怨,因为你的普通 Person 没有 buildADeck 方法。只有木匠型的人才有这种方法。

    当你说

    [(Carpenter *)aPerson buildADeck]
    

    您是在告诉编译器“这个人是木匠。相信我。我知道。请他/她构建一个套牌。”编译器假设你知道你在说什么,并且那个人真的是一个木匠。它会要求 Person 构建一个套牌。

    如果在运行时发现 aPerson 变量指向的 Person 对象不是 Carpenter 类型,程序就会崩溃,因为当你告诉糕点师构建一个套牌时,他/她会非常困惑并且不知道该怎么办。他/她甚至可能会生气并退出。

    如果你说:

    Person *aPerson = [[Carpenter alloc] init];
    

    您告诉编译器创建一个 Carpenter,并将其存储在一个通用的“Person”变量中。编译器很快忘记了它创建的人是木匠。它只知道它是一个人。

    所以,如果你尝试说,编译器会抱怨

    [aPerson buildADeck];
    

    你不得不说

    [(Carpenter *)aPerson buildADeck]
    

    但是,既然所有人都知道怎么做三明治,你可以说

    [aPerson makeMeASandwich] 
    

    并让它工作。即使此人是水管工,他/她也会知道如何制作三明治,并且会(希望)为您制作三明治。如果 Person 是厨师,它可能会是一个更好的三明治,但至少你会得到一个三明治。

    你也可以说

    管道工 *aPlumber = [[管道工分配] init]; [aPlumber makeMeASandwich];

    因为水管工也是人,他们知道如何制作三明治。 (在 OOP 术语中,管道工从其父 Person 类继承 makeMeASandwich 方法。)

    【讨论】:

    • 雄辩,一如既往。对我来说,这就是金钱线:You are telling the compiler to create a Carpenter, and store it in a general-purpose "Person" variable. The compiler promptly forgets that the person it created was a Carpenter. It just knows that it's a Person. 晶莹剔透 - 谢谢。抱歉,有一个但是。在上一篇文章中,在您的替代解决方案中,将对象 (例如标签) 分配给 UIView control 变量:control = aLabel; 的目的是什么?为了访问标签属性,它是否仍然必须转换为标签?
    • 你有一个 switch 语句,它返回各种类型的 UIView 对象。在某些情况下,它返回一个标签,在其他情况下返回一个按钮。由于所有这些都是 UIView 对象,您可以将它们中的任何一个保存到 UIView 类型的变量中。这就是我对您目标的理解......决定创建哪种类型的对象。
    • 好的;我现在明白了。非常感谢邓肯;衷心感谢您对这两篇冗长的帖子的帮助。
    【解决方案3】:

    我对Objctive-C一无所知,但至于一般问题:

    这样想 - 超类的用户(或者实际上 - 编译器或链接器)如何知道它具有这些属性?它被超类类型引用,因此出于所有意图和目的 - 它是超类(尽管有多态性,因为它是一种运行时机制)。

    【讨论】:

      【解决方案4】:

      这与其说是关于继承,不如说是关于您告诉编译器的内容,在这种情况下,view 的类型为UIView *。您可以为该变量分配任何您想要的东西(只要它是一个子类或者您让编译器满意)。

      所以,编译器认为它是UIView *,即使它确实指向UILabel 的实例,所以编译器只对UIView 方法和属性感到满意。

      如果你强制转换,那么编译器会很高兴,如果你对编译器撒谎,那么在运行时事情就会变得一团糟。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-31
        • 2020-10-09
        • 2011-05-23
        • 2015-02-07
        • 2012-01-05
        相关资源
        最近更新 更多