如何根据泛型类型在Swift中使用默认和专用函数?

时间:2016-07-20 16:13:47

标签: swift generics

我有一个通用struct,它接受​​任何类型和函数:

struct Test<T> {
    func makeSomething() -> T? {
        print("Default implementation")
        return nil
    }
}

我还有一个协议,其中包含一个返回其自身实例的静态方法:

protocol TestProtocol {
    static func defaultValue() -> Self
}

如果makeSomething符合T,我希望TestProtocol函数具有专业性。用法示例:

struct TestItem: TestProtocol {
    static func defaultValue() -> TestItem {
        return TestItem()
    }
}

let normalTest: Test<String> = Test()
let normalReturn = normalTest.makeSomething() // Should use default, returns nil.

let specialTest: Test<TestItem> = Test()
let specialReturn = specialTest.makeSomething() // Should use specialised, returns `TestItem`.

我可以想到几种方法,但没有一种方法可行(或者我不知道如何正确实施它们)。

选项1

Test创建一个类型约束的扩展名:

extension Test where T : TestProtocol {
    func makeSomething() -> T? {
        print("Special implementation")
        return T.defaultValue()
    }
}

问题:尝试使用makeSomething函数会导致错误:ambiguous use of 'makeSomething()'。我明白为什么会发生错误;仅仅因为我创建了一个专门的函数,并不意味着默认函数的有效性不高,因此它不知道要使用哪个函数。

也可以将默认实现移动到扩展中,但是它必须具有类型约束,这些约束表示不的任何类型< / em>符合TestProtocol&#39;,据我所知,这是不可能的。

选项2

将特殊部分添加到makeSomething函数,以便根据T的类型更改其行为:

func makeSomething() -> T? {
    if let specialType = T.self as? TestProtocol.Type {
        print("Special implementation")
        return specialType.defaultValue()
    } else {
        print("Default implementation")
        return nil
    }
}

问题:正如预期的那样,这不起作用,因为specialType现在属于TestProtocol.Type类型,而不是T,所以我得到了错误:cannot convert return expression of type 'TestProtocol' to return type 'T?'。在这种情况下,我不知道如何让编译器知道TTestProtocol

我遇到的实际问题更为复杂,但我认为这会简化它并正确说明我遇到的问题。根据上述约束条件中T的一致性,是否有一种方法可以使用默认和专用功能?

2 个答案:

答案 0 :(得分:1)

最简单的解决方案是简单地使用选项#2,然后将结果强制转换回T?,以便弥补差距,我们无法将T.self投射到T.self的抽象类型,也符合TestProtocol.Type

func makeSomething() -> T? {
    if let specialType = T.self as? TestProtocol.Type {
        print("Special implementation")
        return specialType.defaultValue() as? T
    } else {
        print("Default implementation")
        return nil
    }
}

defaultValue返回Self时,此转换应该永远不会失败(我们使用条件转换,因为该方法返回一个可选项)。

虽然如此,依靠运行时类型转换并不是一个特别好的解决方案。实现您在选项#1中尝试实现的重载的一种可能方法是使用协议 - 允许静态调度&amp;当给定的关联类型Something符合TestProtocol时,重载。

// feel free to give this a better name
protocol MakesSomething {
    associatedtype Something
    func makeSomething() -> Something?
}

// default implementation
extension MakesSomething {
    func makeSomething() -> Something? {
        return nil
    }
}

protocol TestProtocol {
    static func defaultValue() -> Self
}

// specialised implementation
extension MakesSomething where Something : TestProtocol {
    func makeSomething() -> Something? { 
        return Something.defaultValue()
    }
}

struct Test<T> : MakesSomething {
    // define T == Something
    typealias Something = T
}

let normalTest : Test<String> = Test()
let normalReturn = normalTest.makeSomething() // nil

let specialTest : Test<TestItem> = Test()
let specialReturn = specialTest.makeSomething() // TestItem()

也许不是最方便的解决方案,因为它涉及创建一个新类型 - 但据我所知,实现条件重载行为的唯一方法是通过协议。虽然取决于您尝试解决的实际问题,但您可以将其集成到现有协议中。

答案 1 :(得分:0)

如果您有多种不同的测试协议,那么在makeSomething()方法中对所有测试协议的依赖可能会变得不方便。

使用两级协议可以帮助保持测试问题与各种测试协议的分离。

 protocol TestSpecialization
 {
    static func makeSomething()->Self?
 }

 class Test<T> 
 {

    init(_ type:T.Type) {}    // the init is merely for syntax candy

    typealias TestedType = T

    func makeSomething() -> TestedType?  
    { 
      // only one exception here rather than one for every test protocol
      if TestedType.self is TestSpecialization.Type 
      {
        return (TestedType.self as! TestSpecialization.Type).makeSomething() as? TestedType
      }
      print("default implementation")
      return nil 
    }
 }


 // all your test protocols should conform to TestSpecialization
 // using a default implementation for makeSomething()
 //
 protocol TestProtocol:TestSpecialization 
 {
     static func defaultValue() -> Self
 }

 extension TestProtocol
 {
     // this is extra work but it keeps the "special" out of the Test<T> class
     static func makeSomething()->Self?
     {
        print("Special implementation")
        return defaultValue()
     }
 }

 // everything else works as expected

 struct TestItem: TestProtocol 
 {
     static func defaultValue() -> TestItem 
     {
         return TestItem()
     }
 }

 let normalTest   = Test(String)
 let normalReturn = normalTest.makeSomething() // uses default, returns nil.

 let specialTest   = Test(TestItem)
 let specialReturn = specialTest.makeSomething() // uses specialised, returns `TestItem`.