Home > Enterprise >  Use generics in place of a dedicated variable enum
Use generics in place of a dedicated variable enum

Time:04-21

I have a protocol. This is implemented by many structs that fall into one of two types of category: TypeOne and TypeTwo. I want to be able to distinguish between their types, so I've added an enum ProtocolType that defines the types typeOne and typeTwo. By default I set the protocolType to be typeOne, but I manually specify typeTwo when it's a TypeTwo struct:

enum ProtocolType {
  case typeOne
  case typeTwo
}

protocol MyProtocol {
  let name: String { get }
  var protocolType: ProtocolType { get }
}

extension MyProtocol {
  var protocolType: ProtocolType {
    return .typeOne
  }
}

enum TypeOne {
  struct Foo: MyProtocol {
    let name = "foo"
  }
}

enum TypeTwo {
  struct Bar: MyProtocol {
    let name = "bar"
    let protocolType = .typeTwo
  }
}

Is there any way I can remove the necessity for defining protocolType in all structs and somehow use generics to identify what type a struct is? They're already defined under the TypeOne and TypeTwo convenience enums, I was wondering if I could utilise that some how?

CodePudding user response:

Given some protocol:

protocol MyProtocol {
  var name: String { get }
}

It sounds like you want to "tag" certain types as special, even though they have the same requirements. That's not an enum, that's just another type (protocol):

// No additional requirements
protocol SpecialVersionOfMyProtocol: MyProtocol {}

You can then tag these at the type level, not the value level:

struct Foo: MyProtocol {
    let name = "foo"
}

struct Bar: SpecialVersionOfMyProtocol {
    let name = "bar"
}

And you can tell the difference using is if you need to:

func f<T: MyProtocol>(x: T) {
    if x is SpecialVersionOfMyProtocol {
        print("special one")
    }
}

In most cases, though, I wouldn't use this kind of runtime check. I'd just have two protocols (one for TypeOne and one for TypeTwo), and implement whatever you need as extensions on those. For example, say you want to print the name differently depending on the type. Start with a protocol that just expresses that:

protocol NamePrintable {
    var name: String { get }
    func printName()
}

func printIt<T: NamePrintable>(x: T) {
    x.printName()
}

Then extend that for TypeOnes and TypeTwos:

protocol TypeOne: NamePrintable {}

extension TypeOne {
    func printName() {
        print("I'm a type one with the name \(name)")
    }
}

protocol TypeTwo: NamePrintable {}

extension TypeTwo {
    func printName() {
        print("I'm a type two with the name \(name)")
    }
}

And conform your structs:

struct Foo: TypeOne {
    let name = "foo"
}

struct Bar: TypeTwo {
    let name = "bar"
}

printIt(x: Foo()) // I'm a type one with the name foo
printIt(x: Bar()) // I'm a type two with the name bar

If you want a default implementation, you can hang it on NamePrintable, but I kind of recommend not doing that for what you've described. I'd probably just have "type one" and "type two" explicitly.

extension NamePrintable {
    func printName() {
        print("BASE FUNCTIONALITY")
    }
}
  • Related