我想将符合协议(protocol)的类作为函数参数传递。具体来说:我有一个广度/深度优先搜索算法,它(分别)基于 Queue 或 Stack 数据结构。我希望有一个函数可以接受我的 Queue 或 Stack 类作为参数。可能包含在一个枚举中,该枚举隐藏了 Queue 和 Stack 对象,以实现更友好的广度优先或深度优先。
我尝试使用 if
或 switch
具有简单字符串的变体作为“深度”或“广度”,但在闭包中声明的变量对于外部范围是不可见的,然后我需要为这两种情况重复几乎整个函数体。
这是一个最小的、可重现的例子。实际的例子更复杂,但我得到了同样的错误,并相信它有同样的潜在问题。
protocol Container {
associatedtype T
var container: [T] { get }
func push(_ thing: T)
}
public class fakeQueue<T>: Container {
internal var container: [T] = [T]()
public func push(_ thing: T) { container.append(thing) }
}
public class fakeStack<T>: Container {
internal var container: [T] = [T]()
public func push(_ thing: T) { container.insert(thing, at: 0) }
}
func addValue<Algorithm: Container>(someValue: String, algorithm: Algorithm) {
// next line raises: Cannot specialize a non-generic definition
let importantVariable = algorithm<String>()
importantVariable.push("Something important")
}
// The call raises: Argument type 'fakeQueue.Type' does not conform to expected type 'Container'
addValue(someValue: "_", algorithm: fakeQueue) // or fakeStack
我知道我不能使用 algorithm<String>()
.在此版本中,它会引发错误:Cannot specialize a non-generic definition.
但是当我使用 fakeStack<String>()
或 fakeQueue<String>()
并简单地避免指定 algorithm: fakeQueue
它按预期工作。
我只是想避免必须创建两个函数。
最佳答案
一些初步注意事项
在 Swift 中传递类型几乎总是一种难闻的味道。 Swift 不会将元类型视为类型。
此外,您的 addValue
永远无法按指定方式编写。您不能将 Container 传入或传出,因为 Container 是一个通用协议(protocol),不能用作类型(例如,在指定函数参数或函数返回类型时)。
您可以创建符合通用协议(protocol)的通用类,从而保证您可以push
到任何此类的实例。但是您无法将它们进一步统一在某个单一的头下,因为它们都是通用的,并且可能会以不同的方式解析。
综上所述,我们可能可以很好地处理您的想法,正如我现在要演示的那样。
修改你的协议(protocol)和类
考虑到您要尝试做的一般事情,我怀疑您正在寻找这样的架构:
protocol Pushable : class {
associatedtype T
init(_ t:T)
var contents : [T] {get set}
func push(_ t:T)
}
final class Stack<TT> : Pushable {
init(_ t:TT) { self.contents = [t]}
var contents = [TT]()
func push(_ t:TT) {
self.contents.append(t)
}
}
final class Queue<TT> : Pushable {
init(_ t:TT) { self.contents = [t]}
var contents = [TT]()
func push(_ t:TT) {
self.contents.insert(t, at:0)
}
}
我将您的容器命名为 Pushable 只是因为调用 push
的能力是我们现在的共同点。您会注意到我已经向 Pushable 协议(protocol)添加了一个 init
;这样我们就有了解决 Pushable 泛型的方法。无论我们用什么值初始化 Pushable,它的类型都会成为它的通用参数化类型;目前,该实例进入 contents
并且可以推送更多实例,但稍后我将展示如何更改它。
所以现在我们可以这样说:
let stack = Stack("howdy")
stack.push("farewell")
let queue = Queue(1)
queue.push(2)
where 子句的乐趣
好的,现在让我们回到将任意值推送到任意 Pushable 的愿望。表达这一点的方法是使用 Pushable,不是作为传递类型或返回类型,而是作为对泛型的约束。这是我们被允许将通用协议(protocol)用于:
func push<TTT,P>(_ what:TTT, to pushable: P)
where P:Pushable, P.T == TTT {
pushable.push(what)
}
一个工厂方法并传递一个元类型
但是您无疑会注意到,我仍然没有提供能够创建 Queue-or-Stack 的函数。为此,我们确实需要传递元类型。啊哈,但我给了 Pushable 一个 init
要求!所以现在我们可以做到:
func createStackOrQueue<TTT,P>(_ what:TTT, type pushableType: P.Type) -> P
where P:Pushable, P.T == TTT {
return P.init(what)
}
let stack = createStackOrQueue("howdy", type:Stack.self)
这与您尝试做的并不完全相同,但也许它足够接近让您继续前进。
工厂方法,只传递元类型
如果你真的坚持传递元类型,让我们改变 init
让它也接受一个元类型:
protocol Pushable : class {
associatedtype T
init(_ t:T.Type)
var contents : [T] {get set}
func push(_ t:T)
}
final class Stack<TT> : Pushable {
init(_ t:TT.Type) { self.contents = [TT]()}
var contents = [TT]()
func push(_ t:TT) {
self.contents.append(t)
}
}
final class Queue<TT> : Pushable {
init(_ t:TT.Type) { self.contents = [TT]()}
var contents = [TT]()
func push(_ t:TT) {
self.contents.insert(t, at:0)
}
}
现在我们可以编写一个与您最初想要的非常接近的通用工厂函数,其中可推送(堆栈或队列)和内容类型都表示为元类型:
func createPushable<TTT,P>(_ whatElementType:TTT.Type, type pushableType: P.Type) -> P
where P:Pushable, P.T == TTT {
return P.init(TTT.self)
}
我不能说我赞成那种事情,但至少你可以看到它是如何完成的。
与您最初的想法非常接近的东西
现在我认为我们可以做出非常接近您最初构想的东西,在那里我们说我们是想要一个堆栈还是一个队列以及要推送到它上面的东西!准备好了吗?
func createPushable<TTT,P>(type pushableType: P.Type, andPush element:TTT) -> P
where P:Pushable, P.T == TTT {
let result = P.init(type(of:element).self)
result.push(element)
return result
}
下面是如何调用它:
let stack = createPushable(type:Stack.self, andPush:"howdy")
关于swift - 如何将类作为函数参数传递?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56402916/