layout | title | category | excerpt | author | translator |
---|---|---|---|---|---|
post |
NSOrderedSet |
Cocoa |
为什吗`NSOrderedSet`不是继承自`NSSet`的捏?答案可能会让你大吃一惊。 |
Mattt |
Candyan |
有个问题:为什吗NSOrderedSet
不是继承自NSSet
的捏?
毕竟 NSOrderedSet
是一个 NSSet
的子类这个逻辑看起来是很完美的。它跟 NSSet
具有相同的方法,还添加了一些 NSArray
风格的方法,像 objectAtIndex:
。据大家所说,它似乎完全满足了里氏替换原则的要求,其大致含义为:
在一段程序中, 如果
S
是T
的子类型,那么T
类型的对象就有可能在没有任何警告的情况下被替换为S
类型的对象。
所以,为什吗NSOrderedSet
是NSObject
的一个子类,而不是NSSet
甚至是NSArray
的子类哪?
可变/不可变的类簇
类簇是 Foundation framework 核心所使用的一种设计模式;也是日常使用 Objective-C 简洁性的本质。
当类簇提供简单的可扩展性的同时,他也把有些事情变得很棘手,尤其是对于一对可变/不可变类像 NSSet
/ NSMutableSet
来说。
正如 Tom Dalling 在这个 Stack Overflow 回答里面巧妙的演示,-mutableCopy
这个方法的创建是跟 Objective-C 对单继承的约束矛盾的。
开始,让我们来看下 -mutableCopy
在类簇中是如果工作的:
NSSet* immutable = [NSSet set];
NSMutableSet* mutable = [immutable mutableCopy];
[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // YES
现在让我们假设下NSOrderedSet
事实上是NSSet
的子类:
// @interface NSOrderedSet : NSSet
NSOrderedSet* immutable = [NSOrderedSet orderedSet];
NSMutableOrderedSet* mutable = [immutable mutableCopy];
[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // NO (!)
{% asset nsorderedset-case-1.svg @inline %}
这样不太好。。。因为这样NSMutableOrderedSet
就不能被用来做NSMutableSet
类型的方法参数了。那么如果我们让NSMutableOrderedSet
作为NSMutableSet
的子类会发生什么事情哪?
// @interface NSOrderedSet : NSSet
// @interface NSMutableOrderedSet : NSMutableSet
NSOrderedSet* immutable = [NSOrderedSet orderedSet];
NSMutableOrderedSet* mutable = [immutable mutableCopy];
[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // YES
[mutable isKindOfClass:[NSOrderedSet class]]; // NO (!)
{% asset nsorderedset-case-2.svg %}
这样也许更糟,因为,现在NSMutableOrderedSet
就不能被用来做NSOrderedSet
类型的方法参数了。
无论怎样去处理它,我们都不能在现有的一对可变/不可变的类上去实现另外一对可变/不可变的类。这在 Objective-C 上是行不通的。
我们可以使用协议来让我们摆脱这个困境(每隔一段时间,多继承的幽灵就会冒出来),而不是自己去承担多继承的风险。 事实上,通过添加以下的协议,Foundation 的集合类可以变的更加面向侧面:
NSArray : NSObject <NSOrderedCollection>
NSSet : NSObject <NSUniqueCollection>
NSOrderedSet : NSObject <NSOrderedCollection, NSUniqueCollection>
然而,为了从这种方式中受益,所有现有的 API 都不得不重新调整来接收一个 id<NSOrderedCollectioin>
参数而不是 NSArray
。但是这个过度将会是痛苦的,并且可能会打开一整条边界。。。这将意味着它可能永远不会被完全的采纳。。。这将意味着在定义你自己的 API 时,没有动力去采用这种方法。。。这样写也太烦了,因为现在有两种相互不兼容的方式来做事情而不是一个。。这。。。
。。。等等,为什么我们起初要使用 NSOrderedSet
哪?
在 iOS 5 和 OS X Lion 中介绍了 NSOrderedSet
。然后,唯一的 API 变化就是在Core Data部分增加了对NSOrderedSet
的支持。
这是对使用 Core Data 的人来说极好的消息,因为它解决了一个长期存在的烦恼(没有办法对一个关系集合做任意的排序)。从前,你不得不添加一个位置
属性,当每次集合被修改时都要重新计算这个属性。没有一个内置的方法去验证你的位置集合是一个唯一的或者没有间隙的序列。
NSOrderedSet
用这种方式对我们的请求做出了回应。
不幸的是,对于 API 的设计者来说,在 Foundation 中它的存在创建了一个在诱人的麻烦和误导之间的东西。
尽管它在 Core Data 中有着非常合适的特殊用例,但对于大多数可能使用它的 API 来说,NSOrderedSet
也许不是一个很好的选择。在那种仅仅需要一个简单的集合对象作为参数传递的情况下,一个简单的 NSArray
对象就可以了—即使这里有个隐含的意思,里面不应该有重复的元素。当顺序对于一个集合参数很重要时,在这种情况下就使用 NSArray
好了(在实现上,应该有代码来处理重复的元素)。如果唯一性很重要,或者对于一个特定的方法来说集合的语义是有意义的,NSSet
仍然是一个很好的选择。
因此,作为一般规则:NSOrderedSet
对于中间和内部表示是有用的,但是你可能不应该把它作为方法参数,除非这是一个特别适用的数据模型。
不说其他的, NSOrderedSet
阐释了 Foundation 在使用类簇上的一些迷人影响。在这样做的时候,他可以让我们更好的理解和权衡简单性和可扩展性,有助于帮助我们做自己的应用设计时做出选择。