19

在 Swift 中,我历来使用扩展来扩展封闭类型并提供方便的、无逻辑的功能,例如动画、数学扩展等。但是,由于扩展是遍布代码库的硬依赖,我总是在实施之前三思而后行作为扩展的东西。

不过,最近我看到 Apple 建议在更大范围内使用扩展,例如将协议实现为单独的扩展。

也就是说,如果你有一个实现协议 B 的类 A,你最终会得到这样的设计:

class A {
    // Initializers, stored properties etc.
}

extension A: B {
    // Protocol implementation
}

当你进入那个兔子洞时,我开始看到更多基于扩展的代码,比如:

fileprivate extension A {
    // Private, calculated properties
}

fileprivate extension A {
    // Private functions
}

我的一部分喜欢在单独的扩展中实现协议时获得的构建块。它使班级的各个部分真正不同。然而,一旦你继承了这个类,你将不得不改变这个设计,因为扩展函数不能被覆盖。

我认为第二种方法是......有趣。它的好处是您不必注释每个私有属性并将其用作私有,因为您可以为扩展指定它。

然而,这种设计也将存储和非存储属性、公共和私有函数分开,使得类的“逻辑”更难遵循(我知道写更小的类)。这与子类化问题一起,让我在扩展仙境的门廊上停了下来。

很想听听世界上的 Swift 社区如何看待扩展。你怎么看?有银弹吗?

4

3 回答 3

11

当然,这只是我的看法,所以我会写的轻松一些。

我目前extension-approach在我的项目中使用它有几个原因:

  • 代码更加简洁:我的类从不超过 150 行,通过扩展的分离使我的代码更具可读性和职责分离

这通常是一个类的样子:

final class A {
    // Here the public and private stored properties
}

extension A {
    // Here the public methods and public non-stored properties
}

fileprivate extension A {
    // here my private methods
}

扩展可以不止一个,当然,这取决于你的类做什么。这对于组织您的代码并从 Xcode 顶部栏中读取它非常有用

扩展描述

  • 它提醒我Swift是一种面向协议的编程语言,而不是 OOP 语言。协议和协议扩展没有什么是你不能做的。而且我更喜欢使用协议为我的类/结构添加安全层。例如,我通常以这种方式编写模型:

    protocol User {
        var uid: String { get }
        var name: String { get }
    }
    
    final class UserModel: User {
        var uid: String
        var name: String
    
        init(uid: String, name: String) {
            self.uid = uid
            self.name = name
        }
    }
    

通过这种方式,您仍然可以在类内部编辑uidnameUserModel,但不能在外部编辑,因为您只会处理User协议类型。

于 2016-11-09T08:04:12.637 回答
5

我使用了类似的方法,可以用一句话来描述:

将类型的职责分类为扩展

这些是我放入单个扩展的方面的示例:

  • 从客户端看到的类型的主界面。
  • 协议一致性(即委托协议,通常是私有的)。
  • 序列化(例如所有NSCoding相关的)。
  • 存在于后台线程上的类型的一部分,例如网络回调。

有时,当单个方面的复杂性上升时,我什至将一个类型的实现拆分到多个文件中。

以下是描述我如何对实现相关代码进行排序的一些细节:

  • 重点是功能成员。
  • 保持公共和私有实现紧密但分开。
  • 不要在var和之间分开func
  • 将功能实现的所有方面放在一起:嵌套类型、初始化器、协议一致性等。

优势

分离类型的各个方面的主要原因是使其更易于阅读和理解。

在阅读外国(或我自己的旧)代码时,了解大局通常是深入研究中最困难的部分。让开发人员了解某种方法的上下文会有很大帮助。

还有另一个好处:访问控制可以更轻松地避免无意中调用某些内容。private可以在“后台”扩展中声明只应该从后台线程调用的方法。现在它根本无法从其他地方调用。

当前限制

Swift 3 对这种风格施加了某些限制。有几件事只能存在于主要类型的实现中:

  • 存储属性
  • 覆盖 func/var
  • 可覆盖的函数/变量
  • 必需的(指定的)初始化程序

这些限制(至少是前三个)来自于需要提前知道对象的数据布局(以及纯 Swift 的见证表)。扩展可能会在运行时延迟加载(通过框架、插件、dlopen 等),并且在创建实例后更改类型的布局会破坏它们的 ABI。

给 Swift 团队的一个适度的建议 :)

保证一个模块的所有代码同时可用。如果 Swift 编译器允许在单个模块中“组合”类型,则可以绕过阻止完全分离功能方面的限制。对于组合类型,我的意思是编译器将从模块内的所有文件中收集定义类型布局的所有声明。与语言的其他方面一样,它会自动找到文件内依赖项。

这将允许真正编写“面向方面”的扩展。不必在主声明中声明存储属性或覆盖将实现更好的访问控制和关注点分离。

于 2016-11-09T15:33:38.203 回答
1

我讨厌它。它增加了额外的复杂性并混淆了扩展的使用,使得人们不清楚人们使用扩展的期望。

如果您使用扩展来实现协议一致性,好的,我可以看到,但为什么不直接评论您的代码呢?这如何更好?我不这么认为。

于 2019-08-27T21:06:07.663 回答