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

relj7zay  于 2022-11-06  发布在  Swift
关注(0)|答案(3)|浏览(181)

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

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)
}

我想做的是形成一个很好的字符串描述,它将包括所有非nil的标题。

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(", ")
}

问题是7种可能的情况中有5种是完全相同的,我有一堆复制粘贴的代码,我认为这是不好的,如果我有100种情况的枚举呢?
有没有办法写这样的东西?

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))
}

......使用某个默认案例来处理所有类似案例?

ruarlubt

ruarlubt1#

虽然我同意Paul的观点,即以这种方式精确地嵌套Location是奇怪的,但基本问题是可以解决的。我还在你的数据上贴上标签;仅仅用两个具有完全不同含义的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中的。在模式匹配中,您不必将元组分开。枚举永远不会有多个关联数据。它们总是只有一个:tuple。(对于函数也是如此。所有函数都接受一个值并返回一个值。该值可能恰好是tuple。)
但是如果你真的想摆脱重复的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要脆弱得多(注意,我打开了崩溃的可能性)。虽然枚举可能是一个很好的工具,但您仍然需要重新考虑这种数据结构。

egdjgwm8

egdjgwm82#

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

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

sbtkgmzw3#

不,Swift的模式匹配没有办法在恰好具有相同关联值的不同枚举值之间进行匹配,这就是你当前问题的答案。
正如Rob明智地建议的那样,从case语句中重构重复的代码是可能的--但是单个case语句不可能跨枚举值进行匹配 * 并 * 提取关联的值。
事实上,你发现自己想要这样做,这表明你可能需要重新考虑你的枚举设计。在许多用例之间有共享的行为和共享的结构,但是枚举用例应该是互斥和独立的。
或许,区域、城市、聚居地、街道、房屋,真的是同一种东西?

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.“为什么我会出现此错误/遇到此障碍?”变成...
1.“为什么编译器会这么想?”变成了......
1.(a)“我在我的模型中对我的问题域的结构做出了哪些Assert?”(b)“这些Assert成立吗?”

相关问题