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

时间:2014-11-22 16:55:03

标签: generics swift

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中,通常的方法是使用瑞士军刀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#的方式做更多的事情,我更喜欢它,因为它允许我们封装青蛙实际存储的方式。由于我们(相当无用)FrogBox只允许我们迭代青蛙,为什么我们关心它们的存储方式呢?

事实证明,在Swift中实现起来有点困难。某些协议只能用作泛型类型约束,因为它们具有关联的类型约束(使用typealias)或在其定义中使用SelfSequenceType同时执行这两项操作,因此我们无法在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中实现所有这些?

1 个答案:

答案 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数组。