iOS中子类的理解

这篇文章跟我以往的文章有点不一样。它主要是一些思想与模式的汇集,而不是一篇指南。下面我所写的模式几乎全都来之不易,都是我犯了错之后才学到的。我并不认为自己是子类方面的权威,但我确实想把我学到的一些东西分享出来。别把本文当做权威指南,它只是一些例子的汇集。

在被问到 OOP(面向对象编程)的时候,Alan Kay(OOP 的发明人)写到:

它跟类无关,但跟消息有关。

然而,很多人的关注点仍然还在类层次上。在本文中,我们会看几个我们可能会把注意力放在创建复杂的类结构上的例子,并给出更有用的替代方案。根据经验,这样会让代码更简单,更易维护。

何时用子类

首先,我们讨论几种使用子类比较合适的场景。

  • 如果你要写一个自定义布局的 UITableViewCell ,那就创建一个子类。这同样适用于几乎每个视图。一旦你开始布局,把这块代码放入子类就更合理一些,不光代码得到了更好的封装,你也能得到一个可在工程之间重用的组件。

  • 假设你的代码是针对多平台多版本的,并且你需要针对每个平台每个版本写一些代码。这时候更合理的做法可能是创建一个 OBJDevice 类,让一些子类如 OBJIPhoneDevice 和 OBJIPadDevice ,甚至更深层的子类如 OBJIPhone5Device 来继承,并让这些子类重写特定的方法。例如,你的 OBJDevice 类可能包含了函数 applyRoundedCornersToView:withRadius ,它有一个默认的实现,但是也能被特定的子类重写。

  • 另一个子类化可能很有用的场景是模型对象(model object)。绝大多数情况下,我的模型对象继承自一个实现了 isEqual: 、 hash 、 copyWithZone: 和 description 等方法的类。这些方法只被实现一次,并且迭代循环遍历所有属性,所以极不容易出错。(如果你也想找一个这样的基类,可以考虑使用 Mantle ,它就是这么做的,并且做得更多。)

何时不使用子类

在以往工作过的很多工程中,我见到过很多继承层次很深的子类。当我也这么干的时候,总会感到内疚。除非继承的层次非常浅,否则你会很快发现它的局限性。

幸运的是,如果你发现自己正在使用深层次的继承,还有很多替代方案可选。在下面的章节中,我们会逐个进行更详细地描述。如果你的子类只是使用相同的接口,协议会是个非常好的替代方案。如果你知道某个对象需要大量的修改,你可能会使用代理来动态改变和配置它。当你想给已有对象增加一些简单功能时,类别可能是个选择。当你有一堆重写了相同方法的子类时,你可以使用配置对象(configuration object)来代替。最后,当你想重用某些功能时,组合多个对象而不是扩展它们可能会更好。

替代方案:协议(Protocols)

很多时候,使用子类的原因是你想保证某个对象可以响应某些消息。假设在 app 里你有一个播放器对象,它可以播放视频。现在你想添加对 YouTube 的支持,使用相同的接口,但是具体实现不同。你可以使像这样用子类来实现:

@class Player : NSObject

- (void)play;
- (void)pause;

@end


@class YouTubePlayer : Player

@end

事实上可能这两个类并没有太多共用的代码,它们只不过具有相同的接口。如果这样的话,使用协议可能会是更好的方案。可以这样用协议来写你的代码:

@protocol VideoPlayer <NSObject>

- (void)play;
- (void)pause;

@end


@class Player : NSObject <VideoPlayer>

@end


@class YouTubePlayer : NSObject <VideoPlayer>

@end

这样,YouTubePlayer 类就不必知道 Player 类的内部实现了。

替代方案:代理(Delegation)

再一次假设你有一个像上面例子中的 Player 类。现在,你想在开始播放的时候在某个地方执行一个自定义的函数。这么做相对容易一些:创建一个自定义的子类,重写 play 方法,调用 [super play ],然后开始做你自定义的工作。这么做是一种方法。另外一种方法是,改动你的 Player 对象,然后给它设置一个代理。如下:

@class Player;

@protocol PlayerDelegate

- (void)playerDidStartPlaying:(Player *)player;

@end


@class Player : NSObject

@property (nonatomic,weak) id<PlayerDelegate> delegate;

- (void)play;
- (void)pause;

@end

现在,在播放器的 play 方法里,就可以给代理发送 playerDidStartPlaying: 消息了。这个 Player 类的任何使用者都可以仅仅实现这个代理协议,而不用继承该该类, Player 类也能够保持通用性。这是个强大有效的技术,苹果在自己的框架里大量地使用它。你想想像 UITextField 这样的类,还有 NSLayoutManager。有时候你还会想把几个不同的方法打包分组到几个单独的协议里,比如 UITableView—— 它不仅有一个代理(delegate),还有一个数据源(dataSource)。

替代方案:类别(Categories)

有时候,你可能会想给一个对象增加一点点额外的功能。比如你想给 NSArray 增加一个方法 arrayByRemovingFirstObject。不用子类,你可以把这个函数放到一个类别里。像这样:

@interface NSArray (OBJExtras)

- (void)obj_arrayByRemovingFirstObject;

@end

在用类别扩展一个不是你自己的类的时候,在方法前添加前缀是个比较好的习惯做法。如果不这么做,有可能别人也用类别对此类添加了相同名字的函数。那时候程序的行为可能跟你想要的并不一样,未预期的事情可能会发生。

使用类别还有另外一个风险,那就是,到最后你可能会使用一大堆的类别,连你自己都会失去对代码全局的认识。假如那样的话,创建自定义的类可能更简单一些。

替代方案:配置对象(Configuration Objects)

在我经常会犯的错误中(现在很快就能发现了),其中有一条是:使用一个含有几个抽象方法的类并让很多子类来重写某个方法。例如,在一个幻灯片应用里,你有一个主题类 Theme ,它有几个属性,比如 backgroundColor 和 font ,还有一些在一张幻灯片上如何布局的逻辑函数。

然后,对每种主题,你都创建一个 Theme 的子类,重写某个函数(例如 setup )并配置其属性。直接使用父类对此做不了什么事。在这种情况下,你可以使用配置对象来让代码更简单些。你可以把共有的逻辑(比如幻灯片布局)放在 Theme 类中,把属性的配置放到较简单的对象中,这些对象中只含有这些属性。

例如,类 ThemeConfiguration 具有 backgroundColor 和 font 属性,而类 Theme 在其初始化函数中获取一个配置类 ThemeConfiguration 的值。

替代方案:组合

组合是代替子类化的最强大有效的方案。如果你想重用已有代码而不想共享同样的接口,组合就是你的首选武器。例如,假设你要设计一个缓存类:

@interface OBJCache : NSObject

- (void)cacheValue:(id)value forKey:(NSString *)key;
- (void)removeCachedValueForKey:(NSString *)key;

@end

简单点的做法是直接继承 NSDictionary,通过调用字典的函数来实现上面的两个方法。

@interface OBJCache : NSDictionary
但是这么做有几个弊端。它本来是应该被详细实现的,但只是通过字典来实现。现在,在任何需要一个 NSDictionary 参数的时候,你可以直接提供一个 OBJCache 值。但如果你想把它转为其它完全不同的东西(例如你自己的库),你就可能需要重构很多代码了。

更好的方式是,将这个字典存在一个私有属性(或者实例变量)中,对外仅仅暴露这两个 cache 方法。现在,当你有了更深入想法的时候,你可以在灵活地修改其实现,而该类的使用者们不用进行重构。

此文章原文链接自己的个人博客: www.koalaliu.com ,因简书平台规范性以及用户量,搬至简书。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,464评论 25 707
  • 叶是普通城镇的一个普通的女孩,像这个城镇其他的女孩一样,因为她是家中的老大,因为她是一个女孩,所以在她下面还有一个...
    Phyllis西阅读 750评论 1 2
  • 俗话说:“一年四季吃枸杞,人可与天地齐寿”。枸杞子是一味很好的滋补品,可清肝明目、滋阴润肺、滋肾益精。 ...
    枸杞行业评论员阅读 289评论 0 1
  • 有时候,听到别人的消息就会慌了神。他们的经历制造了一个幻象——好像生活有无限种可能。世界是五彩缤纷的、花样百出的、...
    seasea阅读 458评论 2 3
  • 正交试验法是研究多因素、多水平的一种试验法,它是利用正交表来对试验进行设计,通过少数的试验替代全面试验,根据正交表...
    CC先生之简书阅读 5,019评论 0 1