我们如何根据用户提供的实例确定协议是否符合特定的子类型,如果不可能这样,任何替代解决方案。
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
}
}
答案 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()
}
}