我有一个通用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`.
我可以想到几种方法,但没有一种方法可行(或者我不知道如何正确实施它们)。
为Test
创建一个类型约束的扩展名:
extension Test where T : TestProtocol {
func makeSomething() -> T? {
print("Special implementation")
return T.defaultValue()
}
}
问题:尝试使用makeSomething
函数会导致错误:ambiguous use of 'makeSomething()'
。我明白为什么会发生错误;仅仅因为我创建了一个专门的函数,并不意味着默认函数的有效性不高,因此它不知道要使用哪个函数。
也可以将默认实现移动到扩展中,但是它必须具有类型约束,这些约束表示不>的任何类型< / em>符合TestProtocol
&#39;,据我所知,这是不可能的。
将特殊部分添加到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?'
。在这种情况下,我不知道如何让编译器知道T
是TestProtocol
。
我遇到的实际问题更为复杂,但我认为这会简化它并正确说明我遇到的问题。根据上述约束条件中T
的一致性,是否有一种方法可以使用默认和专用功能?
答案 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`.