tl;dr
对于那些不想阅读较长解释的人,我可以提出最好的tl;dr
。如果您没有使用具有通用集合接口的静态类型语言,则此问题不适合您。所以这里:
由于我们无法在Swift中创建类型为SequenceType
的属性,如果我们想要隐藏序列的基础类型,我们该怎么办呢?另外,我们如何为可变集合做到这一点?
例如,这不起作用:
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#的方式做更多的事情,我更喜欢它,因为它允许我们封装青蛙实际存储的方式。由于我们(相当无用)FrogBox
只允许我们迭代青蛙,为什么我们关心它们的存储方式呢?
事实证明,在Swift中实现起来有点困难。某些协议只能用作泛型类型约束,因为它们具有关联的类型约束(使用typealias
)或在其定义中使用Self
。 SequenceType
同时执行这两项操作,因此我们无法在Swift的var frogs: SequenceType
实现中说FrogBox
。
这给我们留下了两个选择。首先,我们可以简单地放弃封装并以Objective-C的方式使用数组。或者我们可以使用SequenceOf<T>
,如下所示:
class FrogBox {
private var _frogs = [Frog]()
var frogs: SequenceOf<Frog> {
return SequenceOf<Frog>(_frogs)
}
}
SequenceOf<T>
的文档说它“[f]将操作转发到具有相同Element类型的任意底层序列,隐藏了基础序列类型的细节。”这很好,但这是关于“任意基础序列”的呢?这是否意味着我的元素被复制到任意底层序列?那是什么开销?或者它是说“任意基础序列”是我给它的那个? (我认为后者更有可能,虽然他们应该说“给定的基础序列”。)SequenceOf<T>
可能是最好的答案,但我会犹豫不决,直到我得到澄清。我可以毫不费力地自己动手,但这不是标准的。
那么,Swift的最佳实践是什么?坚持使用Array<T>
的“快速军刀”(冒险暴露出一个应该只是一个序列的东西的可写界面)或者使用SequenceOf<T>
或者其他一些我没想过的方法?此外,我们如何在隐藏底层实现的同时超越简单序列?对于可索引的集合,似乎没有CollectionOf<T>
。我也不知道有哪些C#的IList<T>
等同于封装可以修改的列表。 (更正:Swift似乎MutableCollectionType
符合Array<T>
。但是,没有MutableCollectionOf<T>
,MutableCollectionType
是一个非常贫血的界面。)
如何在Swift中实现所有这些?
答案 0 :(得分:2)
您使用各种语言的示例不会做同样的事情,因此获得您想要的内容取决于您所追求的行为。
您想让您班级的客户看到青蛙的集合,但不要改变集合吗? (你的ObjC示例公开了一个由{NSMutableArray支持的NSArray
。)有一个访问修饰符:
public class FrogBox {
private(set) var frogs = [Frog]()
}
变换集合值在语义上与设置它相同 - 这就是let
vs var
控制可变性的原因。当您使用private(set)
时,有效的情况是,您的班级会将frogs
视为var
,而世界其他地方会将其视为let
。
这与您的ObjC示例的不同之处在于,可变性隐藏在私有接口中。但是,与ObjC不同,Swift在运行时强制执行私有接口。
您想要实现上述目标 和 隐藏基础集合的类型?那么您的SequenceOf<T>
解决方案就是您的解决方案。
正如@Rintaro在聊天中提到的那样,SequenceOf
基本上会传递给它初始化的任何集合的generate()
函数。也就是说,当header-doc注释说“任意基础序列”时,它意味着“任意”部分是你的选择。 (你是对的,“给定基础序列”可能是一个更好的总结。)
您可以通过使用一组引用类型进行测试并修改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
数组。