Search code examples
swiftparsingswitch-statementinstantiation

Avoid the usage of a switch statement when parsing


I have been struggling to develop a code to avoid a big switch statement.

Basically I am given a set of tuples as input and now I would like to instantiate the appropriate class depending on the type specified in the tuple (first element of the tuple - e.g. ("Apple", ...)).

Currently I solved my problem by using a switch statement but this is a very bad idea if the number of classes increase in the future. Is there some elegant way to circumvent this issue?

Thank you!

class Apple {
    var color = ""
}

class Banana {
    var isTasty = false
}

let input = [("Apple", "green"),("Banana", "TRUE")]

for (type, value) in input {
    switch type {
    case "Apple":
        Apple()
    case "Banana":
        Banana()
    default:
        print(value)
    }
}

Solution

  • Check this out,

    // Parent Class to group them
    class Fruit {
        var baseValue: Any
        required init(_ this: Any) {
            baseValue = this
            (self as? Apple)?.color = this as! String
            (self as? Banana)?.isTasty = this as! Bool
            // You can add more subclasses here
        }
    }
    
    class Apple: Fruit, Equatable {
        var color = ""
        
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(color)
        }
        static func == (lhs: Apple, rhs: Apple) -> Bool {
            return lhs.color == rhs.color
        }
    }
    
    class Banana: Fruit, Equatable {
        var isTasty = false
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(isTasty)
        }
        static func == (lhs: Banana, rhs: Banana) -> Bool {
            return lhs.isTasty == rhs.isTasty
        }
    }
    

    It's a little annoying that they have to conform to Fruit, Hashable, and Equatable. But it let's you do this, which answers your question:

    let input: [(Fruit.Type,Any)] = [(Apple.self, "green"),(Banana.self, true)]
    
    for (type, value) in input {
        // Now you don't need that switch statement
        let foo = type.init(value)
        // Now you can use `as?` to find out what it is
        
        print((foo as? Banana)?.isTasty)
        // See, it prints `nil` first, then `Optional(true)` next.
        // Which is how we want it to run
    }
    
    

    However, I think we can do even better.

    enum Fruit {
        case apple(color: String)
        case banana(isTasty: Bool)
    }
    
    let input: [Fruit] = [.apple(color: "red"), .banana(isTasty: true)]
    

    Much better.