[swift-evolution] Strategy on enums - switch might get complex

Rafael Guerreiro guerreiro.dev at gmail.com
Sun Aug 13 13:32:15 CDT 2017


Hello,

If this subject was debated before, please, let me know.
I googled something related to it, but I couldn't find answers on why:

   - Enums only allow literals raw values;
   - It is acceptable using switch, even if it gets big;
      - The problem with switch is that it makes it easy to add more and
      more conditions.

Although the compiler issues a warning when a switch statement covers all
cases, if we use default and later add another case, it will automatically
fall under default. I believe it is a point of failure.
Besides that, methods that use switch can get complex (high cyclomatic
complexity), which can be hard to test.

A simple situation (below) can lead to different implementation for each
case. Which actually means that we should be using the strategy design
pattern.

enum MathematicalOperator {
    case add, subtract, multiply, divide

    func perform(between a: Int, and b: Int) -> Int {
        switch self {
        case .add:      return doAdd(a: a, b: b)
        case .subtract: return doSubtract(a: a, b: b)
        case .multiply: return doMultiply(a: a, b: b)
        case .divide:   return doDivide(a: a, b: b)
        }
    }

    private func doAdd(a: Int, b: Int) -> Int {
        return a + b
    }

    private func doSubtract(a: Int, b: Int) -> Int {
        return a - b
    }

    private func doMultiply(a: Int, b: Int) -> Int {
        return a * b
    }

    private func doDivide(a: Int, b: Int) -> Int {
        return a / b
    }
}

The problem with this approach is that is can be harder to test each case
individually. I know that this example is simple, but it can get complex
once we start adding more cases.

So, to use strategy, I would implement it like the code below, if it would
compile.

protocol Operator {
    func perform(between a: Int, and b: Int) -> Int
}

struct Addition: Operator {
    func perform(between a: Int, and b: Int) -> Int {
        return a + b
    }
}

struct Subtraction: Operator {
    func perform(between a: Int, and b: Int) -> Int {
        return a - b
    }
}

struct Multiplication: Operator {
    func perform(between a: Int, and b: Int) -> Int {
        return a * b
    }
}

struct Division: Operator {
    func perform(between a: Int, and b: Int) -> Int {
        return a / b
    }
}

enum MathematicalOperator: Operator {
    case add = Addition()
    case subtract = Subtraction()
    case multiply = Multiplication()
    case divide = Division()

    func perform(between a: Int, and b: Int) -> Int {
        return rawValue.perform(between: a, and: b)
    }
}

So now I can test each strategy separately, and I can avoid the switch
statement entirely. Also, if I add a new case, I would have to provide a
new strategy, and the protocol would make me implement all methods.

I think that the roadblock for this implementation would be related to
memory, I imagine that's why Swift only allows literals on enums.
Also, this implementation does not care about the Equatable protocol
because its raw value is used only to access the right strategy.

The workaround would be using a struct and not an enum.

This is the first time I'm sending a message, sorry if I broke any
guideline.
- Rafael Guerreiro
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170813/461388d9/attachment.html>


More information about the swift-evolution mailing list