在阅读了大量博客,论坛条目和几个Apple文档之后,我仍然不知道在Objective-C中进行广泛的子类化是否是明智之举.
以下面的例子为例:
说我正在开发一个有很多元素的益智游戏.所有这些元素共享一定量的相同行为.然后,在我的元素集合中,不同的元素组共享相同的行为,区分组和组等...
因此,在确定从什么继承的东西之后,我决定从遗忘中继承.为什么我不应该? 考虑到使用这个模型轻松调整一般行为,我认为我完成了OOP的用途.
但是, - 这是我的问题的来源 - Apple提到使用委托,数据源方法和非正式协议来支持子类化.它真的让我难以理解为什么?
似乎有两个阵营.那些支持子类化的人,那些不支持的子类.这显然取决于个人品味.我想知道大规模子类化的优缺点是什么,而不是大规模的子类化?
总结一下,我的问题很简单:我是对的吗?为什么或为什么不呢?
委托是一种使用组合技术来替换您将子类化的编码的某些方面的方法.因此,它归结为手头任务的古老问题,需要一个知道如何做很多事情的大事,或者你是否有一个松散的专用对象网络(一种非常类型的UNIX责任模型).
使用委托和协议的组合(以定义委托应该能够做什么)提供了很大的行为灵活性和编码的简易性 - 回到Liskov替换原则,当你子类时你必须要小心你没有做任何事情,整个班级的用户会发现意外.但是如果你只是创建一个委托对象,那么你只需要负责,只有你实现的委托方法做了一个协议所要求的,除此之外你不关心.
使用子类仍然有很多很好的理由,如果你真正拥有多个类之间的共享行为和变量,那么它可能对子类很有意义.但是,如果您可以利用委托理念,您通常会使您的课程更容易扩展或以您设计师可能没有预料到的方式使用.
我倾向于更多地使用正式协议而不是非正式协议,因为不仅正式协议确保您将方法视为委托期望的类,而且因为协议定义是一个自然的位置来记录您的内容期望来自实现这些方法的委托.
就个人而言,我遵循这条规则:如果它尊重Liskov替换原则,我可以创建一个子类.
子类化有它的好处,但它也有一些缺点.作为一般规则,我尝试避免实现继承,而是使用接口继承和委托.
我这样做的原因之一是因为当你继承实现时,如果你覆盖方法但不遵守它们(有时没有文档的合同),你可能会遇到问题.另外,我发现具有实现继承的步行类层次结构很困难,因为可以在任何级别覆盖或实现方法.最后,在子类化时,您只能扩展接口,不能缩小它.这导致泄漏抽象.一个很好的例子是java.util.Stack,它扩展了java.util.Vector.我不应该将堆栈视为Vector.这样做只允许消费者在界面上运行.
其他人提到了Liskov替代原则.我认为使用它肯定会清除java.util.Stack问题,但它也可能导致非常深的类层次结构,以确保类只获得它们应该具有的方法.
相反,对于接口继承,基本上没有类层次结构,因为接口很少需要相互扩展.这些类只是实现了它们所需的接口,因此可以由消费者以正确的方式处理.此外,由于没有实现继承,这些类的使用者不会因为以前使用父类的经验而推断他们的行为.
最后,你走哪条路并不重要.两者都完全可以接受.这更多的是你更熟悉的东西以及你正在使用的框架鼓励什么.正如那句老话:"在罗马做罗马人的时候."
在Objective-C中使用继承没有任何问题.Apple使用它相当多.例如,在Cocoa-touch中,UIButton的继承树是UIControl:UIView:UIResponder:NSObject.
我认为马丁在提到利斯科夫替代原则方面提出了重要的观点.另外,正确使用继承要求子类的实现者对超类有深入的了解.如果你曾经努力在复杂的框架中扩展一个非平凡的类,你就知道总有一个学习曲线.此外,超类的实现细节经常"泄漏"到子类,这对于@ $和框架构建器来说是一个巨大的痛苦.
Apple在许多情况下选择使用委托来解决这些问题; 像UIApplication这样的非平凡类通过委托对象公开了公共扩展点,因此大多数开发人员都有更容易学习的曲线和更松散耦合的方式来添加特定于应用程序的行为 - 很少需要直接扩展UIApplication.
在您的情况下,对于您的应用程序特定代码,请使用您熟悉的技术并最适合您的设计.如果使用得当,继承是一个很好的工具.
我经常看到应用程序程序员从框架设计中吸取教训并尝试将它们应用到他们的应用程序代码中(这在Java,C++和Python世界以及Objective-C中很常见).虽然考虑并理解设计者所做的选择是很好的,但这些课程并不总是适用于应用程序代码.