如何将类作为函数参数传递?

时间:2019-06-01 00:11:44

标签: swift

我想将符合协议的类作为函数参数传递。具体来说:我有一个广度/深度优先搜索算法,该算法基于(分别)队列或堆栈数据结构。我希望有一个函数可以接受Queue或Stack类作为参数。可能被包裹在一个枚举中,该枚举隐藏了Queue和Stack对象,从而使宽度优先深度优先

我尝试使用带有简单字符串的ifswitch变体作为“ depth”或“ breadth”,但是在闭包中声明的变量对外部作用域是不可见的,因此我需要重复这两种情况几乎都是整个功能主体。

这是一个最小的,可重复的示例。实际示例更加复杂,但是我遇到了相同的错误,并认为它具有相同的潜在问题。

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时,它将按预期工作。

我只是想避免必须执行两个功能。

1 个答案:

答案 0 :(得分:3)

一些初步注意事项

在Swift中传递类型几乎总是一种不好的气味。 Swift不会将元类型像类型一样对待。

此外,您的addValue永远都不能按照指定的方式写。您不能将Container传入或传出容器,因为Container是通用协议,不能用作类型(例如,在指定函数参数或函数返回类型时)。

您可以创建符合通用协议的通用类,从而保证可以push到任何此类的实例。但是您不能在一个人的掌控下进一步统一它们,因为它们都是泛型并且可能以不同的方式解决。

说了这么多,我们现在可以证明,我们可能可以对您的想法采取一个非常好的方法。

修改协议和类

考虑到您想做的一般事情,我怀疑您是在追求这样的架构:

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协议中添加了init;这样我们就有了一种解决Pushable泛型的方法。无论我们使用哪个值初始化Pushable,其类型都将成为其通用参数化类型;目前,该实例进入contents,并且可以推送其他实例,尽管稍后将说明如何更改。

现在我们可以这样说:

let stack = Stack("howdy")
stack.push("farewell")
let queue = Queue(1)
queue.push(2)

where子句的乐趣

好的,现在让我们回到将任意值推入任意Pushable的愿望。表示的方法是使用Pushable,而不是将其用作传递类型或返回类型,而应将其用作泛型上的 constraint 。我们允许 将通用协议用于以下目的:

func push<TTT,P>(_ what:TTT, to pushable: P) 
    where P:Pushable, P.T == TTT  {
        pushable.push(what)
}

工厂方法并传递元类型

但是您无疑会注意到,我仍然没有提供能够创建队列或堆栈的功能。为此,我们确实需要传递一个元类型。啊哈,但是我给了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)
    }
}

现在,我们可以编写一个通用的工厂函数,使其非常接近您最初使用的函数,其中Pushable(堆栈或队列)和内容类型均表示为元类型:

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