generics - 在 Swift 中实现封装(使用 SequenceType 等)的最佳实践?

标签 generics swift

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 中,通常的方法就是使用瑞士军刀 NSArrayNSMutableArray对于一切,例如,

@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 有 MutableCollectionTypeArray<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/

相关文章:

java - Java 中的数组、列表和泛型

java - Jackson 模块签名阻止为自引用泛型类型添加序列化程序

ios - 最好在约束更改之前或之后调用 .layoutIfNeeded()

ios - 点击时更改 UICollectionViewCell 上的 ImageView 图像

ios - FsCalendar 实现 iOS 中的问题

ios - swift : Dropbox image upload fails due to RegEX pattern match failing

python - 泛型类的类方法

java - 泛型类型转换

ios - 为什么当我按下 RETURN 键时我的键盘没有消失?

java - 创建具有两个以上子级的通用树,每个子级可能具有独特的属性