在运行时确定协议类型

时间:2015-01-03 09:29:35

标签: swift

我们如何根据用户提供的实例确定协议是否符合特定的子类型,如果不可能这样,任何替代解决方案。

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

在另一个API类

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

什么时候打电话

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

api的用户

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

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

我目前在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

        }
    }

3 个答案:

答案 0 :(得分:1)

您正在通过大量的箍来手动重新创建动态调度,即协议和类的目的之一。尝试使用真正的运行时多态来解决您的问题。

拿这段代码:

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

你所说的是,如果它是一个超类,运行超类方法,否则,运行子类。这有点颠倒 - 通常当你是一个子类时,你想要运行子类代码,而不是相反。但假设您将其转换为更传统的顺序,您实质上是在描述调用协议的方法并期望调用适当的实现:

(关闭与手头的问题没有关系,所以现在忽略它们)

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!"

需要考虑的替代方案:消除Sub,并Type2继承Type1。或者,因为这里没有类继承,所以可以使用结构而不是类。

答案 1 :(得分:1)

几乎在您发现自己想要使用is?的任何时候,您可能都想使用枚举。枚举允许您使用相当于is?而不会感觉不好(因为它不是问题)。 is?是错误的OO设计的原因是它创建了一个关闭子类型的函数,而OOP本身总是对子类型开放(您应该将final视为编译器优化,而不是类型的基本部分)。

关闭子类型不是问题或坏事。它只需要在功能范式中思考而不是对象范式。枚举(它是Sum类型的Swift实现)正是这个的工具,并且通常是比子类化更好的工具。

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

现在在go(),而不是is?支票,switch。这不仅是一件坏事,它是必需的并且由编译器进行完全类型检查。

(示例删除了延迟闭包,因为它们实际上不是问题的一部分。)

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

如果你有一些共享行为,只需将它们拉出来放入一个函数中。您可以自由地让“数据对象”实现某些协议,或者是特定类,如果这样可以更容易地传递给共享函数。如果Type2获取恰好是Type1的子类的关联数据,那就没问题了。

如果您稍后再加上Type3,那么编译器会警告您每个switch都没有考虑到这一点。这就是为什么枚举是安全的,而is?则不是。

答案 2 :(得分:0)

您需要从Objective-C世界派生的对象才能执行此操作:

@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
    }
  }
}

编辑:具有不同方法实施的版本:

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()
  }
}