我可以将枚举定义为另一个枚举案例的子集吗?

时间:2016-11-17 12:24:08

标签: swift enums subset restriction

注意: 这与我昨天发布在Stackoverflow上的another one基本相同。但是,我认为我在这个问题中使用了一个不好的例子,并没有把它归结为我心中的本质。由于对原始帖子的所有回复都提到了第一个问题,我认为将新示例放在一个单独的问题中可能是一个更好的主意 - 不需要重复。

可以移动的模型游戏角色

让我们定义在简单游戏中使用的方向枚举:

enum Direction {
    case up
    case down
    case left
    case right
}

现在在游戏中我需要两种角色:

  • 只能左右移动的HorizontalMover
  • 只能上下移动的VerticalMover

他们都可以移动,以便他们都实现

protocol Movable {
    func move(direction: Direction)
}

因此,让我们定义两个结构:

struct HorizontalMover: Movable {
    func move(direction: Direction)
    let allowedDirections: [Direction] = [.left, .right]
}

struct VerticalMover: Movable {
    func move(direction: Direction)
    let allowedDirections: [Direction] = [.up, .down]
}

问题

...使用这种方法是我仍然可以将不允许的值传递给move()函数,例如以下调用有效:

let horizontalMover = HorizontalMover()
horizontalMover.move(up) // ⚡️

当然,我可以在move()函数内部检查是否允许此Mover类型传递direction,否则抛出错误。但是我确实知道在编译时允许哪些情况我还希望检查在编译时发生

所以我真正想要的是:

struct HorizontalMover: Movable {
    func move(direction: HorizontalDirection)
}

struct VerticalMover: Movable {
    func move(direction: VerticalDirection)
}

其中HorizontalDirectionVerticalDirectionDirection枚举的子集枚举。

仅仅像这样独立定义两个方向类型没有任何意义,没有任何共同的"祖先":

enum HorizontalDirection {
    case left
    case right
}

enum VerticalDirection {
    case up
    case down
}

因为那时我不得不一遍又一遍地重新定义相同的情况,这对于表示方向的每个枚举在语义上是相同的。例如。如果我添加另一个可以向任何方向移动的角色,我也必须实现一般方向枚举(如上所示)。然后我在left枚举中出现HorizontalDirection个案,在left枚举词中Direction个案例中彼此不了解哪个在分配和利用我必须在每个枚举中重新分配的原始值时,它不仅是丑陋的,而且成为一个真正的问题。

有没有办法解决这个问题?

我可以将枚举定义为另类枚举的子集吗?

enum HorizontalDirection: Direction {
    allowedCases:
        .left
        .right
}

4 个答案:

答案 0 :(得分:2)

没有。目前,Swift枚举无法实现这一目标。

我能想到的解决方案:

  • 使用我在您的其他问题中概述的协议
  • 回退到运行时检查

答案 1 :(得分:1)

这是一个可能的编译时解决方案:

enum Direction: ExpressibleByStringLiteral {

  case unknown

  case left
  case right
  case up
  case down

  public init(stringLiteral value: String) {
    switch value {
    case "left": self = .left
    case "right": self = .right
    case "up": self = .up
    case "down": self = .down
    default: self = .unknown
    }
  }

  public init(extendedGraphemeClusterLiteral value: String) {
    self.init(stringLiteral: value)
  }

  public init(unicodeScalarLiteral value: String) {
    self.init(stringLiteral: value)
  }
}

enum HorizontalDirection: Direction {
  case left = "left"
  case right = "right"
}

enum VerticalDirection: Direction {
  case up = "up"
  case down = "down"
}

现在我们可以像这样定义一个move方法:

func move(_ allowedDirection: HorizontalDirection) {
  let direction = allowedDirection.rawValue
  print(direction)
}

这种方法的缺点是你需要确保各个枚举中的字符串是正确的,这可能容易出错。我故意使用ExpressibleByStringLiteral,而不是ExpressibleByIntegerLiteral,因为在我看来它更具可读性和可维护性 - 你可能不同意。

你还需要定义所有3个初始化器,这可能有点笨拙,但如果你使用ExpressibleByIntegerLiteral,你会避免这种情况。

我知道你在一个地方交换编译时的安全性,但我认为在某些情况下这种解决方案可能更好。

为了确保您没有任何输入错误的字符串,您还可以添加一个简单的单元测试,如下所示:

XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)

答案 2 :(得分:0)

您可能已经解决了问题,但是对于正在寻找答案的任何人来说,一段时间(不确定苹果公司何时推出),您可以在枚举案例中使用关联的值来对这种状态进行建模。

enum VerticalDirection {
    case up
    case down
}

enum HorizontalDirection {
    case left
    case right
}

enum Direction {
    case vertical(direction: VerticalDirection)
    case horizontal(direction: HorizontalDirection)
}

因此您可以使用如下方法:

func move(_ direction: Direction) {
    print(direction)
}

move(.horizontal(.left))

如果您符合Equatable协议:

extension Direction: Equatable {
    static func ==(lhs: Direction, rhs: Direction) -> Bool {
        switch (lhs, rhs) {
        case (.vertical(let lVertical), .vertical(let rVertical)):
            switch (lVertical, rVertical) {
            case (.up, .up):
                return true
            case (.down, .down):
                return true
            default:
                return false
            }
        case (.horizontal(let lHorizontal), .horizontal(let rHorizontal)):
            switch (lHorizontal, rHorizontal) {
            case (.left, .left):
                return true
            case (.right, .right):
                return true
            default:
                return false
            }
        default:
            return false
        }
    }
}

您可以执行以下操作:

func isMovingLeft(direction: Direction) -> Bool {
    return direction == .horizontal(.left)
}

let characterDirection: Direction = .horizontal(.left)
isMovingLeft(direction: characterDirection) // true
isMovingLeft(direction: characterDirection) // false

答案 3 :(得分:0)

使用Swift协议OptionSet

struct Direction: OptionSet {
let rawValue: int
static let up = Direction(rawValue: 1<<0)
static let right = Direction(rawValue: 1<<1)
static let down = Direction(rawValue: 1<<2)
static let left = Direction(rawValue: 1<<3)
static let horizontal = [.left, .right]
static let vertical = [.up, down]
}
相关问题