我可以将枚举定义为另一枚枚举的子集吗? [英] Can I define an enum as a subset of another enum's cases?

查看:209
本文介绍了我可以将枚举定义为另一枚枚举的子集吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Note: This is basically the same question as another one I've posted on Stackoverflow yesterday. However, I figured that I used a poor example in that question that didn't quite boil it down to the essence of what I had in mind. As all replies to that original post refer to that first question I thought it might be a better idea to put the new example in a separate question — no duplication intended.

我们定义一个简单游戏中使用的方向枚举: p>

Let's define an enum of directions for use in a simple game:

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

现在在游戏中我需要两种字符:

Now in the game I need two kinds of characters:


  • 一个 Horizo​​ntalMover 只能左右移动。

  • 一个 VerticalMover 只能上下移动。

  • A HorizontalMover that can only move left and right.
  • A VerticalMover that can only move up and down.

他们都可以移动实施

They can both move so they both implement the

protocol Movable {
    func move(direction: Direction)
}

所以我们定义两个结构:

So let's define the two structs:

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()函数,例如以下调用将有效:


The Problem

... with this approach is that I can still pass disallowed values to the move() function, e.g. the following call would be valid:

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

当然我可以查看 move() funtion是否允许此Mover类型传递方向,否则会引发错误。但是,由于我确实有编译时允许的信息,我也希望在编译时发生检查

Of course I can check inside the move() funtion whether the passed direction is allowed for this Mover type and throw an error otherwise. But as I do have the information which cases are allowed at compile time I also want the check to happen at compile time.

那么我真的想要的是:

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

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

其中 Horizo​​ntalDirection VerticalDirection 的子集 - 方向枚举

只要像这样定义两个方向类型就没有什么意义,没有任何常见的祖先

It doesn't make much sense to just define the two direction types independently like this, without any common "ancestor":

enum HorizontalDirection {
    case left
    case right
}

enum VerticalDirection {
    case up
    case down
}



Horizo​​ntalDirection 枚举和左边中留下 c $ c> case在一般的方向枚举,不知道对方不仅是丑陋,但在分配和使用原始值时成为一个真正的问题我必须在每个枚举中重新分配。

because then I'd have to redefine the same cases over and over again which are semantically the same for each enum that represents directions. E.g. if I add another character that can move in any direction, I'd have to implement the general direction enum as well (as shown above). Then I'd have a left case in the HorizontalDirection enum and a left case in the general Direction enum that don't know about each other which is not only ugly but becomes a real problem when assigning and making use of raw values that I would have to reassign in each enumeration.

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

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


推荐答案

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

Here's a possible compile-time solution:

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 方法:

Now we can define a move method like this:

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

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

The drawback of this approach is that you need to make sure that the strings in your individual enums are correct, which is potentially error-prone. I have intentionally used ExpressibleByStringLiteral for this reason, rather than ExpressibleByIntegerLiteral because it is more readable and maintainable in my opinion - you may disagree.

您还需要定义所有3个初始化器,这可能有点笨拙,但是如果您使用 ExpressableByIntegerLiteral 代替。

You also need to define all 3 of those initializers, which is perhaps a bit unwieldy, but you would avoid that if you used ExpressibleByIntegerLiteral instead.

我知道你在一个地方交换编译时的安全性,但我想这样

I'm aware that you're trading compile-time safety in one place for another, but I suppose this kind of solution might be preferable in some situations.

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

To make sure that you don't have any mistyped strings, you could also add a simple unit test, like this:

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

这篇关于我可以将枚举定义为另一枚枚举的子集吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆