【问题标题】:Calling protocol default implementation from regular method, when protocol has associated type当协议具有关联类型时,从常规方法调用协议默认实现
【发布时间】:2016-08-27 17:00:02
【问题描述】:

我有一个协议,它有一个带有默认参数的静态方法。我想更改实现协议的类中的默认值。基本上是在做类和超级容易做的事情。
我只有在协议没有关联类型时才有解决方案。

以下代码有效,但一旦您取消注释关联的类型声明,它就无法编译。

protocol Protocol {
//    associatedtype AssociatedType
}

extension Protocol {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello(name: String = "Stack Overflow") {
        // Uncommenting the Protocol.AssociatedType causes:
        // Protocol can only be used as a generic constraint because it has associated type requirements
        (self as Protocol).sayHello(name)
    }
}

Class<()>().sayHello()

我明白为什么它不能编译:Protocol 没有 AssociatedType 的具体类型。
所以也许这个问题应该是“我可以明确地专门化一个协议吗?”,我相信答案是否定的。

我有一个部分解决方法。但即使它有效,它也很糟糕。
特别是当您考虑到我正在编写一个 sayHello 是公共的库时,因此以下解决方法迫使我有第二个协议,它必须是公共的,但没用。
这是解决方法:

protocol Parent {}

protocol Protocol: Parent {
    associatedtype AssociatedType
}

extension Parent {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello(name: String = "Stack Overflow") {
        (self as Parent).sayHello(name)
    }
}

Class<()>().sayHello()

但这对我不起作用,因为我的 sayHello 使用关联类型。所以它不能被提取到另一个协议中。

为了确保我清楚,这就是我想要的,只是用类代替协议:

class Protocol<T> {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol<T> {
    override func sayHello(name: String = "Stack Overflow") {
        super.sayHello(name)
    }
}

Class<()>().sayHello()

【问题讨论】:

    标签: swift protocols associated-types default-implementation


    【解决方案1】:

    您正在尝试重新发明协议中的继承,但没有这样的事情。但是得到你在说什么是微不足道的。只是说你的意思。你的意思不是“我想做我继承的事情”。你的意思是“我想做一些常见的行为”。只需为该常见行为提供一个名称。这消除了您所指的所有歧义。

    protocol Protocol {
            associatedtype AssociatedType
    }
    
    extension Protocol {
        // Put the default behavior on the protocol, not on the instance
        // Of course you could also put it on the instance if that were convenient.
        static func defaultSayHello(_ name: String = "World") {
            print("Hello, \(name)!")
        }
    
        // If you want a default on the instance, too, provide one that we an override
        func sayHello(_ name: String = "World") {
            Self.defaultSayHello(name)
        }
    }
    
    class Class<T>: Protocol {
        typealias AssociatedType = T
    
        func sayHello(name: String = "Stack Overflow") {
            // Now the default behavior lives on my type
            Class.defaultSayHello(name)
        }
    }
    
    // But other types can get default behavior
    class OtherClass<T>: Protocol {
        typealias AssociatedType = T
    }
    
    Class<()>().sayHello() // Hello, Stack Overflow!
    OtherClass<()>().sayHello() // Hello, World!
    

    关于这一点的一个令人沮丧的部分是 Swift 无法将defaultSayHello 限制为Protocol 的实现者。所以从技术上讲,任何人都可以调用它。有时值得在它前面加上 _ 以表明外人不应该这样做。这是协议中一个基本的访问控制问题,与这个具体问题无关;当你想要“我的实现者可以在自己身上使用的东西,但不应该随机调用的东西”时,它总是会出现。 Swift 目前没有解决方案。

    【讨论】:

    • 我不想接受这个答案,因为正如你所指出的,它污染了公共 API,我真的想避免这种情况。不过它确实启发了我,所以我发布了自己的答案(也没有回答问题)。
    【解决方案2】:

    受 Rob Napier 回答的启发,我选择了以下内容;旧的默认重载:

    protocol Protocol {
        associatedtype AssociatedType
    }
    
    extension Protocol {
        func sayHello(name: String = "World") {
            print("Hello, \(name)!")
        }
    }
    
    class Class<T>: Protocol {
        typealias AssociatedType = T
    
        func sayHello() {
            self.sayHello("Stack Overflow")
        }
    }
    
    Class<()>().sayHello()      // Hello, Stack Overflow!
    Class<()>().sayHello("you") // Hello, you!
    

    这确实符合我的需要,但不能回答问题。所以我不是 100% 满意。
    我相信 Rust 通过允许使用 X&lt;T&gt; 和相关类型的通用特性/协议来做到这一点。

    【讨论】:

      猜你喜欢
      • 2015-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多