【问题标题】:Is subclassing in Objective-C a bad practice?Objective-C 中的子类化是一种不好的做法吗?
【发布时间】:2009-05-09 22:21:32
【问题描述】:

在阅读了大量博客、论坛条目和几个 Apple 文档后,我仍然不知道在 Objective-C 中进行大量子类化是否明智

以下面的案例为例:

假设我正在开发一款益智游戏 有很多元素。所有这些 元素共享一定数量的 相同的行为。然后,在我的 元素的集合,不同 元素组共享相等 行为,区分群体 团体等...

所以,在确定继承什么之后 从什么开始,我决定子类化 的遗忘。为什么不呢? 考虑到易于调整一般 这个模型的行为,我 认为我完成了一些面向对象的事情 意味着。

但是, - 这就是我的问题的来源 - Apple 提到使用委托、数据源方法和非正式协议来支持子类化。这真的让我难以置信,为什么?

似乎有两个阵营。那些支持子类化的,那些不支持的。这显然取决于个人口味。 我想知道大规模子类化和不大规模子类化的优缺点是什么

总结一下,我的问题很简单:我说的对吗?为什么或为什么不?

【问题讨论】:

  • Kriem,Apple 谈论的是总体上使用 Cocoa,与 Objective-C 无关。对于每个单独的项目,您必须决定如何最好地设置您的应用程序和代码库。在 Apple 的案例中,他们已经使用 MVC 和 IoC 范例设置了 Cocoa(尤其是 AppKit/UIKit),因此他们建议您不要在可以使用委托时自己继承诸如 NSControl 等之类的东西。总结一下:这个警告是专门针对 Cocoa 框架的,一般来说不是针对 Objecive-C 的。
  • @Jason:这是一个很好的答案,为什么只留下一个简单的评论?
  • 没有 Cocoa 的 Objective-C?我敢肯定世界上有近五个人在使用它。
  • @Kriem - Chuck 可能只是意味着大多数人将 Objective-C 与 Cocoa 一起使用,但这仍然不是重点。如果这对您的解决方案最有效,请随意设计您的游戏/拼图/任何带有大量子类的东西。 Cocoa 框架本身被设计成不需要子类化,仅此而已。
  • 这个问题已经存在多年,但似乎没有提到深层层次结构最明显的问题。世界很少像问题所暗示的那样完全分裂。当你的元素有点像子类 A 和子类 B 时,你会怎么做?如果你在发现这个组件时已经有了很深的层次结构,那么它就是一个真正的 PITA 来处理。

标签: objective-c oop subclass


【解决方案1】:

委派是一种使用组合技术来替换您本来可以子类化的编码的某些方面的方法。因此,它归结为一个古老的问题,即手头的任务需要一件知道如何做很多事情的大事,或者如果你有一个松散的专用对象网络(一种非常 UNIX 的责任模型)。

使用委托和协议的组合(定义委托应该能够做什么)提供了极大的行为灵活性和易于编码 - 回到 Liskov 替换原则,当你继承你有要小心,不要做任何让全班的用户感到意外的事情。但是,如果您只是制作一个委托对象,那么您需要承担的责任要少得多,只需您实现的委托方法执行一个协议所要求的,除此之外您并不关心。

仍然有很多使用子类的充分理由,如果您确实在多个类之间共享行为和变量,那么子类可能会很有意义。但是,如果您可以利用委托概念,您通常可以使您的类更容易扩展或以设计者可能没有预料到的方式使用。

我更喜欢正式协议而不是非正式协议,因为正式协议不仅可以确保您拥有一个类将您视为委托的方法,而且因为协议定义是一个自然的地方记录您对实现这些方法的委托的期望。

【讨论】:

    【解决方案2】:

    就我个人而言,我遵循这条规则:如果它尊重Liskov substitution principle,我可以创建一个子类。

    【讨论】:

    • 很好的信息。我认为这适用于我的情况。但是, - 这是我的问题的来源 - Apple 提到使用委托、数据源方法和非正式协议来支持子类化。这真的让我大吃一惊,为什么? - 我将编辑问题以强调这一点。
    • 我怀疑在大多数情况下使用委托要简单得多,因此苹果推荐。子类化可能是合适的,但更难做到正确。只是我的意见。
    • 我认为这个定义非常笼统。如果您通读数学,它的基本意思是任何使用超类的东西都应该能够使用子类而无需更改。因此,与其帮助理解何时拥有子类,不如说你应该避免子类破坏一个类。该定义还完全消除了所有常见的子类类别——比如永远不会直接使用的抽象超类,只能子类化......
    • @Kendall - 这不是真的。这种方法当然不会消除这些类别。 NSArray 就是这样设计的——作为一个抽象类,具有未知的替代子类作为实际实现。
    【解决方案3】:

    子类化有它的好处,但它也有一些缺点。作为一般规则,我尽量避免实现继承,而是使用接口继承和委托。

    我这样做的原因之一是,当您继承实现时,如果您覆盖方法但不遵守它们(有时是未记录的合同),您可能会遇到问题。此外,我发现具有实现继承的遍历类层次结构很困难,因为方法可以被覆盖或在任何级别实现。最后,子类化时只能扩大接口,不能缩小接口。这会导致抽象泄漏。一个很好的例子是扩展 java.util.Vector 的 java.util.Stack。我不应该将堆栈视为向量。这样做只允许消费者在界面周围跑来跑去。

    其他人提到了里氏替换原则。我认为使用它肯定会解决 java.util.Stack 问题,但它也可能导致非常深的类层次结构,以确保类只获得它们应该具有的方法。

    相反,接口继承基本上没有类层次结构,因为接口很少需要相互扩展。这些类只是实现了它们需要的接口,因此可以由消费者以正确的方式处理。此外,由于没有实现继承,因此这些类的消费者不会因为之前使用父类的经验推断他们的行为。

    最后,你走哪条路并不重要。两者都是完全可以接受的。这实际上更多的是您更喜欢什么以及您正在使用的框架鼓励什么。古语有云:“在罗马做罗马人做的事。”

    【讨论】:

      【解决方案4】:

      在 Objective-C 中使用继承并没有错。苹果使用它相当多。比如在 Cocoa-touch 中,UIButton 的继承树是 UIControl : UIView : UIResponder : NSObject 。

      我认为 Martin 在提到 Liskov 替换原则时提到了一个重要的点。此外,正确使用继承要求子类的实现者对超类有深入的了解。如果您曾经努力在复杂的框架中扩展一个重要的类,您就会知道总有一条学习曲线。此外,超类的实现细节经常“泄漏”到子类,这对框架构建者来说是一个很大的痛点。

      Apple 在许多情况下选择使用委托来解决这些问题;像 UIApplication 这样的重要类通过委托对象公开公共扩展点,因此大多数开发人员都有更容易的学习曲线和更松散的耦合方式来添加应用程序特定的行为——很少需要直接扩展 UIApplication。

      在您的情况下,对于您的应用程序特定代码,请使用您最熟悉并最适合您的设计的技术。如果使用得当,继承是一个很好的工具。

      我经常看到应用程序程序员从框架设计中吸取教训并尝试将它们应用到他们的应用程序代码中(这在 Java、C++ 和 Python 世界以及 Objective-C 中很常见)。虽然思考和理解框架设计者所做的选择是件好事,但这些经验并不总是适用于应用程序代码。

      【讨论】:

        【解决方案5】:

        一般来说,如果存在完成您想做的事情的委托等,您应该避免子类化 API 类。在您自己的代码中,子类化通常更好,但它确实取决于您的目标,例如。如果您提供 API,则应提供基于委托的 API,而不是假设子类化。

        在处理 API 时,子类化有更多潜在的错误——例如。如果类层次结构中的任何类获得了一个与您添加的名称相同的新方法,那么您就会破坏东西。而且,如果你提供了一个有用的/辅助类型的函数,那么将来有可能将类似的东西添加到你子类化的实际类中,这可能会更有效,等等,但你的覆盖会隐藏它。

        【讨论】:

          【解决方案6】:

          请阅读 Apple 文档"Adding behavior to a Cocoa program"!。在 “继承自 Cocoa 类” 部分下,请参阅第 2 段。 Apple 明确提到子类化是向框架添加应用程序特定行为的主要方式(请注意,FRAMEWORK)。
          MVC 模式并不完全不允许使用子类或子类型。至少我没有从苹果或其他人那里看到这个建议(如果我错过了,请随时向我指出正确的信息来源)。如果您仅在应用程序中子类化 api 类,请继续,没有人阻止您,但请注意它不会破坏整个类/api 的行为。子类化是扩展框架 api 功能的好方法。我们在 Apple IOS 框架 API 中也看到了很多子类化。 作为开发人员,必须注意实现是有据可查的,并且不会被其他开发人员意外复制。如果您的应用程序是一组您计划作为可重用组件分发的 API 类,那么这完全是另一场球赛。

          恕我直言,与其询问最佳实践是什么,不如先彻底阅读相关文档,实施并测试它。做出你自己的判断。你最清楚自己在做什么。 其他人(比如我和其他许多人)很容易从网络上阅读不同来源的东西并抛出术语。做你自己的判断,到目前为止它对我有用。

          【讨论】:

            【解决方案7】:

            我真的认为这取决于你想要做什么。如果您在示例中描述的益智游戏确实具有一组具有共同属性的独特元素,并且没有提供符合您需求的类 - 例如,“NSPuzzlePiece”,那么我认为没有问题广泛地进行子类化。

            根据我的经验,当 Apple 提供的类已经完成了与您希望它做的事情接近的事情时,委托、数据源方法和非正式协议会更有用。

            例如,假设您正在构建一个使用表格的应用。有(我在这里说的是 iPhone SDK,因为这是我有经验的地方)一个 UITableView 类,它可以完成创建用于与用户交互的表的所有细节,并且为UITableView 的实例,而不是完全继承 UITableView 并重新定义或扩展其方法以自定义其行为。

            委托和协议也有类似的概念。如果您可以将您的想法融入 Apple 的类中,那么这样做并使用数据源、委托和协议通常比创建您自己的子类更容易(并且会更顺利地工作)。它可以帮助您避免额外的工作和浪费时间,并且通常不易出错。 Apple 的课程负责使功能高效和调试的业务;与他们合作得越多,从长远来看,您的程序所犯的错误就越少。

            【讨论】:

              【解决方案8】:

              我对 ADC 强调“反对”子类化的印象更多地与操作系统如何演变的遗留问题有关......回到那天(Mac Classic aka os9),当时 c++ 是大多数 mac 的主要接口工具箱,子类化是事实上的标准,以便程序员修改普通操作系统功能的行为(这确实有时令人头疼,这意味着必须非常小心,任何和所有修改的行为都是可预测的,并且没有破坏任何标准行为)。

              话虽如此,我对 ADC 对子类化的强调的印象不是提出设计应用程序的类层次结构没有继承的案例,而是指出在新的做事方式(即 OSX)在大多数情况下有更合适的方法来自定义标准行为而无需子类化。

              因此,无论如何,尽可能稳健地设计您的拼图程序的架构,并在您认为合适的时候利用继承!

              期待看到您酷炫的新拼图应用程序!

              |K

              【讨论】:

              • 这是一个有趣的想法,但实际上 Cocoa 的主要设计原则早在 C++ 成为 Mac OS 的通用编程语言之前就已经确立。 Cocoa 从 NEXTSTEP 获得了对委托和组合的重视,之所以这样设计,是因为负责人认为组合是用可重用组件构建大型软件系统的一种更可靠的方式。这个想法真的可以追溯到 Smalltalk 和 Model-View-Conroller 设计模式。
              • 在 Taligent 项目中学到的脆弱基类的痛苦教训中也有一些元素。那是一个 Apple-IBM 联合项目,在某种程度上,它试图创建一个用于开发应用程序的整体框架。他们的人物、地点和事物比喻值得研究,只是为了深入了解框架设计。该项目可能是 C++ 作为基本框架设计语言失败的最大证明(我是说作为一名专业的 C++ 开发人员)。
              • 轻微修正,C++ 从来都不是大多数 MacOS 工具箱的主要接口。它总是 C 和 Pascal。
              【解决方案9】:

              Apple 确实似乎被动地不鼓励使用 Objective-C 进行子类化。

              这是 OOP 设计的一个公理,即倾向于组合而不是实现。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-05-31
                • 1970-01-01
                • 2019-01-24
                • 2013-05-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-07-16
                相关资源
                最近更新 更多