objc:Issue13——Architecture【4】

子类化——by Chris Eidhof

这篇文章和我通常写的文章有所不同。它不是一个指南更像是一系列想法和模式。我将要讲述的差不多所有模式都是通过犯错误这样艰难的方式找出来的。绝不是说我就是子类化方面的权威,但是,我只是想把我学到的一些事情分享出来。不要把它作为一个权威指南,而是一个例子集合。

当被问及面向对象编程时(OOP),Alan Kay(创造者)写到,他不是关于类的,而是消息传递^?。 但,仍然有很多人关注于创建类层次。在这篇文章中,我们将看几个有用的案例,但是我们主要关注创建复杂的类层次结构。在我们的经验中,这会让代码更加简单,更容易维护。关于这个话题有很多文章,你可以在Clean CodeCode Complete这样的书中找到,推荐大家阅读这两本书。

什么时候子类化

首先,让我们讨论一下在哪些情况下创建子类是有意义的。如果你正在构建一个有自定义布局的UITableViewCell,那么你需要创建一个子类。几乎所有的观点都是如此;一旦你开始布局,把它移到子类里就变得有意义了,这样你不仅优雅的整理了你的代码,还让代码在整个项目都可复用。

假设,你的代码是针对不同的平台和版本的,你需要为每个平台和版本编写自定义的部分。这时创建一个OBJDevice类就有意义了,它可以子类化出OBJIphoneDeviceOBJIPadDevice,甚至可以更深层的子类化出OBJIPhone5Device,它们可以重写特定的方法。例如,你的OBJDevice类可以包含applyRoundedCornersToView:withRadius:方法。它有一个默认的实现,但是这个方法可以被特定的子类重写。

另外一种非常有用的子类化情形是在模型对象(model object)中。绝大多数时候,模型对象从类中继承的实现方法有isEqual:hashcopyWithZone:description。这些方法通过对属性迭代实现一次,很难出错。(如果你想找这样的基类,可以考虑用Mantle,它正事这样做的,而且不仅仅如此。)

什么时候不子类化

我参与过很多的项目,我也见过深层次的子类化。很惭愧我也这样做过。除非层次很浅,否则很快就会达到极限。

幸运的是,如果你发现自己正处于这样深的层次,这有很多可选方案。在下面的小节中,我们会深入分析每一部分。如果你的子类只是共享相同的接口和协议,这是一个很好的选择。如果你知道一个对象需要修改很多地方,你可能需要使用委托动态的改变和配置它。当你想要对已存在的对象扩展一些简单地功能时,类别(category)可能是一个选择。当你有一系列子类,每一个都重写(override)同样的方法,你可能会使用配置对象。最后,当你想重用一些功能时,最好组合多个对象而不是扩展他们。

可选方案

方案:协议

通常的,一个使用子类的原因是你想要保证一个对象响应一个确定的消息。设想一下,在一个应用中你有一个可以播放视频的播放器(player)对象。现在,如果你想增加YouTobe的支持,你需要同样的接口,但是不同的实现。一种是用子类实现的方式如下:

@interface Player: NSObject
- (void)play;
- (void)pause;
@end

@interface YouTobePlayer: Player
@end

也许,这两个类并没有共享太多代码,仅仅是是相同的接口。在这种情况下,使用协议可能是一个更好的解决方案。使用协议,你可能会写下如下代码:

@protocol VideoPlayer <NSObject>
- (void)play;
- (void)pause;
@end

@interface Player: NSObjet <VideoPlayer>
@end

@interface YouTobePlayer: NSObject <VideoPlayer>
@end

这种情况下,YouTobePlayer不需要知道Player的内部情况。

方案:委托

再一次假设,你有一个像上面例子中那样的Player类。现在,在一个地方,你想要在paly方法中执行一个自定义动作。做到它相当的容易:你可以创建一个自定义子类,重写paly方法,调用[super paly],然后执行自定义的功能。这是一种处理方式。另外一种是改变Player对象并给他一个委托。例如:

@class Player;
@protocol PlayerDelegate
- (void)playerDidStartPlaying:(Player *)player;
@end

@interface Player: NSObject
@property (nonatomic, weak) id<PlayerDelegate> delegate;
- (void)play;
- (void)pause;
@end

现在,在播放器的play方法中,委托获得了playerDidStartPlaying:消息。这个类的任何使用者只需实现委托协议而不需要创建子类,并且Player对象依然保持着通用性。这是一个非常强大的技术,苹果在他们自己的框架中大量的使用了该技术。有时,你想把不同的方法组织到不同的协议中,如UITableView做的那样,它不仅有委托还有数据源。

方案:类别(Category)

有时,你可能想对一个对象进行一点功能扩展。假设,你想向NSArray中扩展一个arrayByRemovingFirstObject方法。你可以把它放入类别中,而不是创建一个子类。像这样:

@interface NSArray (OBJExtras)
- (void)obj_arrayByRemovingFirstObject;
@end

当使用类别扩展一个不是你自己创建的类时,在方法前加前缀是一个很好的做法。如果不这样做,其他人可能会使用同样的技术实现同样的一个方法。然后,如果行为不匹配,可能会有意想不到的事情发生。

使用类别的危险之一是,你的项目可能最终会使用大量的类别,这样你可能会丢失你的概述(you can lose your overview.)。这种情况下,创建一个自定义类可能更简单。

方案:配置对象

我一直在犯的一个错误(现在可以很快的意识到)是:会创建一个带有很多抽象方法的类,然后很多子类都会重写一个特定的方法。例如,在一个演示应用程序,你可能会一个Theme类,他有几个属性,如:backgroundColorfont,还有一些在幻灯片上放置东西的逻辑。

然后,对于每一个主题,你创建一个Theme的子类,重写一个方法(如:setup),并配置属性。直接使用子类没有意义。这种情况下,你可以使用配置对象让你的代码更加简单。你可以把共享逻辑放在Theme类中(如:幻灯片布局),但是把配置放在一个仅仅有属性的简单对象中。

例如,一个ThemeConfiguration类有backgroundColorfont属性,并且Theme类在初始化的时候获取一个这个类的实例。

方案:组合

最强大的子类化的替代方案是组合。如果你想重用已存在的代码但是没有共享同样的接口,组合是你可以选择的“武器”。例如,假设你正在设计一个缓存类:

@interface OBJCache: NSObject
- (void)cacheValue:(id)value forKey:(NSString *)key;
- (void)removeCacheValueForKey:(NSString *)key;
@end

达到此目的的一个简单方式是子类化NSDictionary并且通过调用字典方法实现这两个方法。

@interface OBJCache: NSDictionary

然而,这存在几个缺点。用字典实现的事实应该是一个实现细节。现在,在任何需要一个NSDictionary类型参数的地方,你都可以提供一个OBJCache值。如果你想换一种完全不同的东西(例如,你自己的类库),你需要重构很多代码。

一个好的方式是把这个字典存放到一个私有属性中(或者实例变量中),并且仅仅暴露出两个cache方法。现在,你可以维持灵活性,当你获取更多的见解时可以随意修改实现,并且类的使用者不需要进行重构。

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

推荐阅读更多精彩内容