Swift 2:有没有办法在枚举的switch语句中使用'default'和相关的值?

时间:2015-10-30 15:54:39

标签: swift enums swift2 enumeration

我有一个递归枚举,其中大多数情况都有相同类型的关联值:

indirect enum Location {
    case Title(String?)
    case Region(Location)
    case Area(Location, Location)
    case City(Location, Location)
    case Settlement(Location, Location)
    case Street(Location, Location)
    case House(Location, Location)
}

我想要做的是形成一个很好的字符串描述,其中包括所有非零标题。

func getStringFromLocation(location: Location) -> String? {
    var parts: [String?] = []

    switch location {
    case .Title(let title): return title
    case .House(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Street(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Settlement(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .City(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Area(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Region(let title):
        parts.append(getStringFromLocation(title))
    }

    return parts
        .filter { $0 != nil }
        .map { $0! }
        .joinWithSeparator(", ")
}

问题在于,七个可能的案例中有五个完全相同,我有一堆复制粘贴的代码,正如我想的那样,并不好。如果我列举了一百个案例怎么办?

有没有办法写这样的东西?

switch location {
case .Title(let title): 
    parts.append(title)
case .Region(let title):
    parts.append(getStringFromLocation(title))
default (let title, let parent):
    parts.append(getStringFromLocation(parent))
    parts.append(getStringFromLocation(title))
}

...使用一些默认情况来处理所有类似的情况?

3 个答案:

答案 0 :(得分:1)

虽然我同意保罗的担心,以这种方式嵌套Location很奇怪,但基本问题是可以解决的。就个人而言,我不会用default来解决它,我只是简化代码并使用Swift给我们的工具(比如CustomStringConvertible;我也在你的数据上添加标签;这太令人困惑了只有两个Location元素具有完全不同的含义):

indirect enum Location: CustomStringConvertible {
    case Title(String?)
    case Region(Location)
    case Area(title: Location, parent: Location)
    case City(title: Location, parent: Location)
    case Settlement(title: Location, parent: Location)
    case Street(title: Location, parent: Location)
    case House(title: Location, parent: Location)

    var description: String {

        func format(locs: (Location, Location)) -> String {
            return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
        }

        switch self {
        case .Title(let title): return title ?? ""

        case .Region(let title): return "\(title)"

        case .House(let data):      return format(data)
        case .Street(let data):     return format(data)
        case .Settlement(let data): return format(data)
        case .City(let data):       return format(data)
        case .Area(let data):       return format(data)
        }
    }
}

注意我如何将整个元组卸载到data。你不必在模式匹配中打破元组。枚举永远不会有多个关联数据。他们总是只有一个:一个元组。 (函数也是如此。所有函数都接受一个值并返回一个值。该值可能恰好是一个元组。)

但如果你真的想摆脱重复return format(data),那么你可以通过Mirror。 (你可以通过Mirror来解决相当令人震惊的事情。在你做之前你应该非常小心。这种情况只是重复输入,而不是重复的逻辑。一些重复的输入不是你应该创造的很多东西删除的复杂性。)

以下是你的表现:

var description: String {
    switch self {
    case .Title(let title): return title ?? ""

    case .Region(let title): return "\(title)"

    default:
        let m = Mirror(reflecting: self)
        guard let locs = (m.children.first?.value as? (Location, Location)) else {
            preconditionFailure("Unexpected data in enum. Probably missing a case somewhere.")
        }
        return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
    }
}

这里的教训是,枚举的第一个孩子是其所有数据的元组。

但是使用Mirror要脆弱得多(注意我打开了崩溃的可能性)。虽然枚举可能是一个很好的工具,但你仍然可能想重新考虑这个数据结构。

答案 1 :(得分:1)

对于这个老问题的现代读者来说,您实际上可以绑定当今几个案例中的相同关联值。只要类型匹配即可。

case .house(let title, let parent), .street(let title, let parent):
    parts.append(getStringFromLocation(parent))
    parts.append(getStringFromLocation(title))

答案 2 :(得分:0)

不,Swift的模式匹配没有办法匹配碰巧具有相同关联值的不同枚举值。这就是你当前问题的答案。

正如Rob明智地建议的那样,有可能从case语句中重构重复的代码 - 但是单个case语句不可能在枚举值之间匹配提取相关的值。 / p>

您发现自己想要这样做的事实表明您可能想重新考虑您的枚举设计。许多案例之间存在共享行为和共享结构,但枚举案例应该是相互排斥和独立的。

也许Area,City,Settlement,Street和House都是同样的东西?

indirect enum Location {
    case Title(String?)
    case Region(Location)
    case BinaryLocation(BinaryKind, Location, Location)

    enum BinaryKind {
        case Area
        case City
        case Settlement
        case Street
        case House
    }
}

(我不明白这两个相关位置的含义是什么,但是既然你这样做了,我建议你提出比BinaryLocationBinaryKind更多的解释性名称。)

对于枚举来说,这甚至可能不是一个合适的情况;例如,这样的事情可能会更好:

protocol Location {
    var description: String { get }
}

struct Title: Location {
    var title: String?

    var description: String {
        return title
    }
}

// ... and one for Region, and then ...

protocol BinaryLocation {
    var child0: Location { get }
    var child1: Location { get }
}

extention BinaryLocation: Location {
    var description: String {
        return "\(child0), \(child1)"
    }
}

// ...and then either individual structs for House, Street, etc., or
// an enum like BinaryKind above.

我不能说,因为我不知道你的整个情况。

可以所说的是你对重复代码的关注是有效的,而这个具体问题是回过头来看看模型大图的线索。以挑剔型严格性问题作为重新思考您的建模选择的线索是“迅捷的方式:”

  1. “为什么我得到这个错误/撞到这堵墙?”变成......
  2. “为什么编译器会这么想?”变成......
  3. (a)“我在模型中对我的问题域的结构做了哪些断言?”(b)“这些断言是否会阻止?”