17

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

以下面的案例为例:

假设我正在开发一个包含很多元素的益智游戏。所有这些元素都具有一定数量的相同行为。然后,在我的元素集合中,不同的元素组共享相同的行为,将组与组区分开来,等等......

所以,在确定什么从什么继承之后,我决定子类化。为什么我不应该? 考虑到使用此模型可以轻松调整一般行为,我认为我完成了面向对象编程的目标。

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

好像有两个阵营。那些支持子类化的,那些不支持的。这显然取决于个人口味。我想知道大规模子类化而不是大规模子类化的利弊是什么

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

4

9 回答 9

12

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

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

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

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

于 2009-05-10T06:02:34.070 回答
10

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

于 2009-05-09T22:25:43.950 回答
7

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

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

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

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

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

于 2009-05-10T05:26:26.123 回答
6

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

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

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

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

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

于 2009-05-09T23:24:54.977 回答
4

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

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

于 2009-05-09T23:16:12.657 回答
4

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

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

于 2012-10-13T19:24:45.760 回答
3

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

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

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

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

于 2009-05-09T23:18:57.910 回答
2

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

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

所以,无论如何,尽可能健壮地设计你的拼图程序的架构,在你认为合适的时候利用继承!

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

|K<

于 2009-05-11T13:45:16.510 回答
0

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

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

于 2014-01-19T10:51:01.707 回答