tl;dr
这是最好的tl;dr
我可以为那些不想阅读较长解释的人提供建议。如果您尚未使用具有通用集合接口(interface)的静态类型语言,那么这个问题不适合您。所以这里是:
因为我们无法创建 SequenceType
类型的属性在 Swift 中,如果我们想隐藏序列的底层类型,我们该怎么办?另外,我们如何对可变集合执行此操作?
例如,这不起作用:
class FrogBox {
var _frogs = [Frog]()
var frogs: SequenceType {
return _frogs
}
}
那么,如果我们想隐藏我们正在使用数组的事实,特别是因为我们不希望用户直接修改数组,我们该怎么办?
长解释
在我职业生涯的不久阶段,我编写了大量 C# 代码,尽管 Objective-C 和 Ruby 现在更加常见。在 C# 中,如果我想将可迭代集合公开为类的属性,我将声明为 IEnumerable<T>
,例如,
class FrogBox {
private readonly ArrayList<Frog> _frogs = new ArrayList<Frog>();
public IEnumerable<Frog> frogs { get { return _frogs; } }
}
另一方面,在 Objective-C 中,通常的方法就是使用瑞士军刀 NSArray
或NSMutableArray
对于一切,例如,
@interface FrogBox
@property (nonatomic, readonly) NSArray *frogs;
@end
@implementation FrogBox {
NSMutableArray *_frogs;
}
@dynamic frogs;
- (instancetype)init {
self = [super init];
if (self) {
_frogs = [NSMutableArray array];
}
return self;
}
- (NSArray*)frogs {
return _frogs;
}
@end
在 Swift 中,我们有机会按照 C# 的方式做更多事情,我更喜欢这样做,因为它允许我们封装 Frog 的实际存储方式。由于我们的(相当无用)FrogBox
只允许我们迭代 Frog ,为什么我们关心它们是如何存储的?
事实证明,这在 Swift 中实现起来有点困难。某些协议(protocol)只能用作泛型类型约束,因为它们具有关联的类型约束(使用 typealias
)或使用 Self
在他们的定义中。 SequenceType
两者兼而有之,所以我们不能说 var frogs: SequenceType
在我们实现FrogBox
在 swift 。
这给我们留下了两个选择。首先,我们可以简单地放弃封装并使用数组,以 Objective-C 的方式。或者我们可以使用 SequenceOf<T>
如下:
class FrogBox {
private var _frogs = [Frog]()
var frogs: SequenceOf<Frog> {
return SequenceOf<Frog>(_frogs)
}
}
SequenceOf<T>
的文档说它“将操作转发到具有相同元素类型的任意底层序列,隐藏底层序列类型的细节。”。这很棒,但是“任意底层序列”是什么?这是否意味着我的元素被复制到任意底层序列?这样做的开销是多少?或者说“任意底层序列”是我给它的? (我认为后者更有可能,尽管他们应该说“给定的底层序列”。) SequenceOf<T>
可能是最好的答案,但在得到澄清之前我犹豫是否使用它。我可以自己推出,不会有太多麻烦,但这不是标准的。
那么,Swift 的最佳实践是什么?坚持使用“swift 军刀”Array<T>
(并且有暴露可写接口(interface)的风险,而这些接口(interface)应该只是一个序列)或使用 SequenceOf<T>
或者也许是我没有想到的其他方法?另外,我们如何超越简单的序列,同时仍然隐藏底层实现?似乎没有CollectionOf<T>
用于可索引集合。也没有任何与 C# 的 IList<T>
等效的东西。我知道,封装一个可以修改的列表。 (更正:看来 Swift 有 MutableCollectionType
, Array<T>
符合。但是,没有 MutableCollectionOf<T>
,并且 MutableCollectionType
是一个相当贫乏的接口(interface)。)
如何在 Swift 中实现这一切?
最佳答案
来自不同语言的示例不会执行相同的操作,因此获得您想要的结果取决于您所追求的行为。
您想让您类(class)的客户看到 Frog 的集合,但不改变集合? (您的 ObjC 示例公开了由 `NSMutableArray 支持的 NSArray
。)有一个访问修饰符:
public class FrogBox {
private(set) var frogs = [Frog]()
}
改变集合值在语义上与设置它相同 - 这就是 let
的原因与 var
控制可变性。当您使用private(set)
时,实际上发生的事情是你的类(class)看到 frogs
作为var
而世界其他地方则将其视为 let
。
这与您的 ObjC 示例没有什么不同,因为可变性隐藏在私有(private)接口(interface)中。但与 ObjC 不同的是,Swift 在运行时强制执行私有(private)接口(interface)。
您想要实现上述目标并隐藏底层集合的类型吗?那么你的SequenceOf<T>
解决方案是最好的解决方案。
正如 @Rintaro 在聊天中提到的,SequenceOf
本质上传递到 generate()
它初始化的任何集合的函数。也就是说,标题文档注释中说“任意底层序列”,这意味着“任意”部分是您的选择。 (你是对的,“给定基础序列”可能是一个更好的总结。)
您可以通过使用引用类型数组进行测试并修改 SequenceOf
中的成员来亲自看到这一点。访问器:
class Frog {
var name: String
init(name: String) { self.name = name }
}
class FrogBox: Printable {
private var _frogs = [Frog]()
var frogs: SequenceOf<Frog> {
return SequenceOf<Frog>(_frogs)
}
init() {
_frogs.append(Frog(name: "Murky"))
_frogs.append(Frog(name: "Lurky"))
}
func change() {
_frogs[0].name = "Gurky"
}
var description: String {
return join(", ", _frogs.map({ frog in frog.name }))
}
}
let box = FrogBox()
// description -> "Murky, Lurky"
for frog in box.frogs { frog.name = "Gurky" }
// description -> "Gurky, Gurky"
如果构建 SequenceOf
复制了底层序列,改变其成员之一不会改变原始的 _frogs
FrogBox
中的数组.
关于generics - 在 Swift 中实现封装(使用 SequenceType 等)的最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27079952/