我可以将枚举定义为另一个枚举案例的子集吗?
Posted
技术标签:
【中文标题】我可以将枚举定义为另一个枚举案例的子集吗?【英文标题】:Can I define an enum as a subset of another enum's cases? 【发布时间】:2017-03-31 23:46:45 【问题描述】:注意:这与我昨天在 *** 上发布的 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)
其中HorizontalDirection
和VerticalDirection
是Direction
枚举的子集枚举。
像这样独立定义两个方向类型没有多大意义,没有任何共同的“祖先”:
enum HorizontalDirection
case left
case right
enum VerticalDirection
case up
case down
因为我必须一遍又一遍地重新定义相同的情况,这些情况对于每个表示方向的枚举在语义上是相同的。例如。如果我添加另一个可以向任何方向移动的角色,我还必须实现大方向枚举(如上所示)。然后我在HorizontalDirection
枚举中有一个left
案例,在一般Direction
枚举中有一个left
案例,它们彼此不了解,这不仅丑陋,而且在分配和分配时成为一个真正的问题利用我必须在每次枚举中重新分配的原始值。
那么有没有办法解决这个问题?
我可以像这样将一个枚举定义为另一个枚举的案例的子集吗?
enum HorizontalDirection: Direction
allowedCases:
.left
.right
【问题讨论】:
相关:Can an enum contain another enum values in Swift? 【参考方案1】:没有。 Swift 枚举目前无法做到这一点。
我能想到的解决方案:
使用我在您的其他问题中概述的协议 回退到运行时检查【讨论】:
【参考方案2】:这是一个可能的编译时解决方案:
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)
【讨论】:
【参考方案3】:您可能已经解决了您的问题,但对于任何正在寻找答案的人来说,一段时间以来(不确定 Apple 何时引入它),您可以在枚举案例中使用关联值来模拟这些类型的状态。
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
【讨论】:
【参考方案4】:使用 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]
【讨论】:
以上是关于我可以将枚举定义为另一个枚举案例的子集吗?的主要内容,如果未能解决你的问题,请参考以下文章
HDU 5616 Jam's balance(暴力枚举子集)