美文网首页
为什么不要在枚举和 Equatable 中使用 default

为什么不要在枚举和 Equatable 中使用 default

作者: 梁杰_numbbbbb | 来源:发表于2017-03-21 20:56 被阅读197次

    作者:Ole Begemann,原文链接,原文日期:2017-03-06
    译者:Cwift;校对:numbbbbb;定稿:CMB

    假设你有一个 Swift 的枚举:

    enum Expression {
        case number(Double)
        case string(String)
    }
    

    你希望它遵守 Equatable 协议。由于该枚举具有关联值,必须手动添加,所以需要实现 == 函数

    extension Expression: Equatable {
        static func ==(lhs: Expression, rhs: Expression)
            -> Bool {
            switch (lhs, rhs) {
            case let (.number(l), .number(r)): return l == r
            case let (.string(l), .string(r)): return l == r
            default: return false
            }
        }
    }
    

    这里处理了参数类型相同的两种情况,比较类型不同时会执行 default case 并返回 false。这种做法简单直接,也没错:

    Expression.number(1) == .number(1) // → true
    Expression.number(1) == .string("a") // → false
    

    Default case 会使得枚举对穷尽的检查无效

    然而这段代码有一个严重的缺陷:如果你在枚举中添加另一个 case,编译器不会警告你当前枚举的实现是不完整的。现在给枚举添加第三种 case:

    enum Expression {
        case number(Double)
        case string(String)
        case bool(Bool)
    }
    

    只要能通过编译器的检查,那么这种行为就是合理的。但执行以下操作时代码会返回错误的结果:

    Expression.bool(true) == .bool(true) // → false!
    

    switch 语句中的 default case 会使编译器对枚举的检查无效。所以,通常来说尽可能避免在 switch 语句中使用 default。

    <center><font size=4>如果可能,尽量不要在 switch 语句中使用 default</font></center>

    模式匹配大爆炸

    没有 default case 的缺点也很明显:你需要写更多的模式匹配代码。下面是完全覆盖三种 case 的写法:

    extension Expression: Equatable {
        static func ==(lhs: Expression, rhs: Expression)
            -> Bool {
            switch (lhs, rhs) {
            case let (.number(l), .number(r)): return l == r
            case let (.string(l), .string(r)): return l == r
            case let (.bool(l), .bool(r)): return l == r
            case (.number, .string),
                 (.number, .bool),
                 (.string, .number),
                 (.string, .bool),
                 (.bool, .number),
                 (.bool, .string): return false
            }
        }
    }
    

    o(>﹏<)o!写起来一点都不愉快,而且当枚举的 case 增加时会变得更糟。switch 语句必须区分的状态数会随着枚举中 case 的数量呈平方增长。

    从平方增长到线性增长

    _ 占位符可以简化你的 switch 语句。虽然不能使用 default 语句,但是我们可以把上一段代码最后的六行简化成三行:

    extension Expression: Equatable {
        static func ==(lhs: Expression, rhs: Expression)
            -> Bool {
            switch (lhs, rhs) {
            case let (.number(l), .number(r)): return l == r
            case let (.string(l), .string(r)): return l == r
            case let (.bool(l), .bool(r)): return l == r
            case (.number, _),
                 (.string, _),
                 (.bool, _): return false
            }
        }
    }
    

    这个方案更好,枚举中每增加一个 case,switch 语句只会增加两行,不再是平方级的增加。而且你保留了编译器穷尽检查的优势:添加一个新 case,编译器会报 == 的错误。

    Sourcery

    如果你觉得重复代码还是太多,可以看看 Krzysztof Zabłocki 开发的代码生成工具 Sourcery。在类似的应用场景中,它可以自动为枚举以及其他类型生成 Equatable 协议所需的代码(并且不断更新生成代码)。

    本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

    相关文章

      网友评论

          本文标题:为什么不要在枚举和 Equatable 中使用 default

          本文链接:https://www.haomeiwen.com/subject/wutnnttx.html