Determining protocol types at runtime

How to determine if a protocol matches a specific subtype based on user provided instances, if not possible, any alternative solutions.

API

protocol Super {}

protocol Sub: Super {} //inherited by Super protocol

class Type1: Super {} //conforms to super protocol

class Type2: Type1, Sub {} //conforms to sub protocol

      

inside another API class

func store(closures: [() -> Super]) {
    self.closures = closures
}

      

when is the time to call

func go() {
    for closure in closures {
        var instance = closure()
        if instance is Super {
            //do something - system will behave differently
        } else { //it Sub
            //do something else - system will behave differently
        }
    }
}

      

api users

class Imp1: Type1 {}
class Imp2: Type2 {}

var closures: [() -> Super] = [ { Imp1() }, { Imp2() } ]
store(closures)

      

my current workaround in the API

func go() {
        for closure in closures {
            var instance = closure()
            var behavior = 0
            if instance as? Type2 != nil { //not so cool, should be through protocols
                behavior = 1         //instead of implementations
            }


            if behavior == 0 { //do something within the api,

            } else { //do something else within the api

            }

            //instance overriden method will be called 
            //but not important here to show, polymorphism works in here
            //more concerned how the api can do something different based on the types

        }
    }

      

+1


source to share


3 answers


You are jumping through many hoops to manually recreate the dynamic dispatch i.e. one of the purposes of protocols and classes. Try to use real runtime polymorphism to solve your problem.

Take this code:

    if instance is Super {
        //do something
    } else { //it Sub
        //do something else
    }

      

What you say, if it is a superclass, run the superclass method, otherwise run the subclass. This is a bit flipped - usually when you are a subclass, you want to run the subclass code, not the other way around. But, assuming you are moving to a more traditional order, you are basically describing a protocol method call and expect the appropriate implementation to be called:



(closing arent really related to the question in hand, so ignoring them for now)

protocol Super { func doThing() }
protocol Sub: Super { }  // super is actually a bit redundant here

class Type1: Super {
    func doThing() {
        println("I did a super thing!")
    }
}

class Type2: Sub {
    func doThing() {
        println("I did a sub thing!")
    }
}

func doSomething(s: Super) {
    s.doThing()
}

let c: [Super] = [Type1(), Type2()]

for t in c {
    doSomething(t)
}

// prints "I did a super thing!", then "I did a sub thing!"

      

Alternatives to consider: eliminate Sub

and inherit Type2

from Type1

. Or, since there is no class inheritance here, you can use structs rather than classes.

+1


source


Almost every time you find what you want to use is?

, you should probably use an enum. Enums allow you to use the equivalent is?

without feeling bad (that's not a problem). The reason is?

OO is badly designed is because it creates a function that is closed to subtyping, whereas OOP itself is always open to subtyping (you should think of final

as a compiler optimization, not a fundamental part of types).

Closing subtyping is not a problem or a bad thing. It just requires thinking in a functional paradigm, not a subject paradigm. Enums (which are Swift's implementation of Sum) are exactly the tool for doing this, and are often a better tool than subclasses.

enum Thing {
    case Type1(... some data object(s) ...)
    case Type2(... some data object(s) ...)
}

      

Now go()

instead of checking is?

you switch

. Not only is it not bad, it is required and fully type-checked by the compiler.



(The example removes lazy closures as they are not part of the question.)

func go(instances: [Thing]) {
    for instance in instances {
        switch instance {
            case Type1(let ...) { ...Type1 behaviors... }
            case Type2(let ...) { ...Type2 behaviors... }
        }
    }
}

      

If you have common behaviors, just pull them into a function. You can let your "data objects" implement specific protocols or be concrete classes if this makes it easier to pass shared functionality. It is okay if it Type2

accepts associated data that is a subclass Type1

.

If you come back later and add Type3

, then the compiler will warn you about anyone switch

who cannot consider it. This is why enums are safe and is?

not.

+1


source


This requires objects derived from the Objective-C world:

@objc protocol Super {}

@objc protocol Sub: Super {}

class Parent: NSObject, Super {}

class Child: NSObject, Sub {}

func go( closures: [() -> Super]) {
  for closure in closures {
    let instance = closure()
    if instance is Sub { // check for Sub first, check for Super is always true
      //do something
    } else {
      //do something else
    }
  }
}

      

Edit : version with different method implementations:

protocol Super {
  func doSomething()
}

protocol Sub: Super {}

class Parent: Super {
  func doSomething() {
    // do something
  }
}

class Child: Sub {
  func doSomething() {
    // do something else
  }
}

func go( closures: [() -> Super]) {
  for closure in closures {
    let instance = closure()
    instance.doSomething()
  }
}

      

0


source







All Articles