Swift 我是否可以将一个枚举定义为另一个枚举的子集';什么案子?

Swift 我是否可以将一个枚举定义为另一个枚举的子集';什么案子?,swift,enums,subset,restriction,Swift,Enums,Subset,Restriction,注意:这与我昨天在Stackoverflow上发布的问题基本相同。然而,我想我在那个问题上用了一个糟糕的例子,并没有把它归结为我所想的本质。由于对那篇原始帖子的所有回复都提到了第一个问题,我认为最好将新的例子放在一个单独的问题中——不要重复 模拟可以移动的游戏角色 让我们定义一个用于简单游戏的方向枚举: enum Direction { case up case down case left case right } 现在在游戏中我需要两种角色: 只能左右移

注意:这与我昨天在Stackoverflow上发布的问题基本相同。然而,我想我在那个问题上用了一个糟糕的例子,并没有把它归结为我所想的本质。由于对那篇原始帖子的所有回复都提到了第一个问题,我认为最好将新的例子放在一个单独的问题中——不要重复


模拟可以移动的游戏角色 让我们定义一个用于简单游戏的方向枚举:

enum Direction {
    case up
    case down
    case left
    case right
}
现在在游戏中我需要两种角色:

  • 只能左右移动的
    水平移动器。
    ← →
  • 只能上下移动的
    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()
函数中检查传递的
方向是否允许用于此移动器类型,否则会抛出错误。但由于我确实掌握了在编译时允许哪些情况的信息,我也希望检查在编译时进行

所以我真正想要的是:

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

struct VerticalMover: Movable {
    func move(direction: VerticalDirection)
}
其中
水平方向
垂直方向
方向
枚举的子集枚举

像这样单独定义两个方向类型,没有任何共同的“祖先”,没有多大意义:

因为这样我就必须一次又一次地重新定义相同的情况,对于表示方向的每个枚举,这些情况在语义上是相同的。例如,如果我添加了另一个可以向任何方向移动的字符,我还必须实现常规方向枚举(如上所示)。然后,我在
水平方向
枚举中有一个
案例,在一般
方向
枚举中有一个
案例,它们彼此不了解,这不仅丑陋,而且在分配和使用原始值时成为一个真正的问题,我必须在每个枚举中重新分配原始值


那么,有没有办法解决这个问题呢? 我可以将一个枚举定义为另一个枚举的一个子集吗

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

不可以。目前无法使用Swift枚举

我能想到的解决办法是:

  • 使用我在你的另一个问题中概述的协议
  • 回退到运行时检查

    • 这里有一个可能的编译时解决方案:

      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)
      }
      
      XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
      XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
      XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
      XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)
      
      func move(_ direction: Direction) {
          print(direction)
      }
      
      move(.horizontal(.left))
      
      func isMovingLeft(direction: Direction) -> Bool {
          return direction == .horizontal(.left)
      }
      
      let characterDirection: Direction = .horizontal(.left)
      isMovingLeft(direction: characterDirection) // true
      isMovingLeft(direction: characterDirection) // false
      
      这种方法的缺点是,您需要确保各个枚举中的字符串是正确的,这可能容易出错。出于这个原因,我特意使用了
      ExpressibleByStringLiteral
      ,而不是
      ExpressibleByIntegerLiteral
      ,因为在我看来,它更易于阅读和维护-您可能不同意

      您还需要定义所有3个初始值设定项,这可能有点笨拙,但如果改用
      ExpressibleByIntegerLiteral
      ,则可以避免这种情况

      我知道您在用一个地方的编译时安全性来换取另一个地方的编译时安全性,但我认为这种解决方案在某些情况下可能更可取

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

      func move(_ allowedDirection: HorizontalDirection) {
        let direction = allowedDirection.rawValue
        print(direction)
      }
      
      XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
      XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
      XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
      XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)
      
      func move(_ direction: Direction) {
          print(direction)
      }
      
      move(.horizontal(.left))
      
      func isMovingLeft(direction: Direction) -> Bool {
          return direction == .horizontal(.left)
      }
      
      let characterDirection: Direction = .horizontal(.left)
      isMovingLeft(direction: characterDirection) // true
      isMovingLeft(direction: characterDirection) // false
      

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

      enum VerticalDirection {
          case up
          case down
      }
      
      enum HorizontalDirection {
          case left
          case right
      }
      
      enum Direction {
          case vertical(direction: VerticalDirection)
          case horizontal(direction: HorizontalDirection)
      }
      
      因此,您可以使用如下方法:

      func move(_ allowedDirection: HorizontalDirection) {
        let direction = allowedDirection.rawValue
        print(direction)
      }
      
      XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
      XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
      XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
      XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)
      
      func move(_ direction: Direction) {
          print(direction)
      }
      
      move(.horizontal(.left))
      
      func isMovingLeft(direction: Direction) -> Bool {
          return direction == .horizontal(.left)
      }
      
      let characterDirection: Direction = .horizontal(.left)
      isMovingLeft(direction: characterDirection) // true
      isMovingLeft(direction: characterDirection) // false
      
      如果您符合Equalable协议:

      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 move(_ allowedDirection: HorizontalDirection) {
        let direction = allowedDirection.rawValue
        print(direction)
      }
      
      XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
      XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
      XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
      XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)
      
      func move(_ direction: Direction) {
          print(direction)
      }
      
      move(.horizontal(.left))
      
      func isMovingLeft(direction: Direction) -> Bool {
          return direction == .horizontal(.left)
      }
      
      let characterDirection: Direction = .horizontal(.left)
      isMovingLeft(direction: characterDirection) // true
      isMovingLeft(direction: characterDirection) // false
      

      使用Swift协议

      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]
      }
      
      struct方向:OptionSet{
      让rawValue:int
      静态松弛=方向(原始值:1相关: