【问题标题】:Reasons to include function in protocol definition vs. only defining it in the extension?在协议定义中包含函数与仅在扩展中定义函数的原因?
【发布时间】:2016-01-13 22:01:42
【问题描述】:

采用以下协议和扩展:

protocol ProtocolA {
    func myFunc()
}

extension ProtocolA {
    func myFunc() {
        print("Default ProtocolA implementation.")
    }
}

这与将函数完全排除在协议定义之外有什么区别(如果有的话),如下所示:

protocol ProtocolB { }

extension ProtocolB {
    func myFunc() {
        print("Default ProtocolB implementation.")
    }
}

我发现了一个不同之处。如果我定义了一个覆盖默认实现的结构,我只能将它转换为协议并在我将函数排除在定义之外时调用协议的实现:

struct A: ProtocolA {
    func myFunc() {
        print("Struct A's implementation.")
    }
}

struct B: ProtocolB {
    func myFunc() {
        print("Struct B's implementation.")
    }
}

A().myFunc()                   // "Struct A's implementation."
(A() as ProtocolA).myFunc()    // "Struct A's implementation."

B().myFunc()                   // "Struct B's implementation."
(B() as ProtocolB).myFunc()    // "Default protocol implementation."

换句话说,如果您像ProtocolB 那样将函数从协议定义中取出,那么您可以通过将对象转换为协议来访问默认实现。另一方面,如果您将函数留在协议定义中,则无法强制转换为协议以获得默认协议行为。

将函数定义排除在协议之外似乎可以在行为方面提供最大的灵活性。

有什么缺点?如果你把函数从协议定义中去掉,你会失去什么?您是否完全失去了任何功能?

【问题讨论】:

    标签: swift protocols


    【解决方案1】:

    将函数声明为协议定义的一部分会指示编译器在调用函数时使用动态分派,因为编译器会期望实现协议的类型为该函数提供实现。这称为method requirement。现在,如果类型没有定义方法,那么运行时会将方法调用解析为协议扩展中声明的方法。

    然而,在协议扩展中声明函数only告诉编译器他不需要使用动态调度,而是使用静态调度,这更快,但没有'不能很好地使用多态性,因为即使符合协议的类型也实现了方法,也会调用协议扩展实现。

    为了举例说明上述内容,让我们考虑以下代码:

    protocol Shape {
        func draw()
    }
    
    extension Shape {
        func draw(){
            print("This is a Shape")
        }
    }
    
    struct Circle: Shape {
        func draw() {
            print("This is a Circle")
        }
    }
    
    struct Square: Shape {
        func draw() {
            print("This is a Square")
        }
    }
    
    let shapes: [Shape] = [Circle(), Square()]
    
    for shape in shapes {
        shape.draw()
    }
    

    上面的代码会有输出

    This is a Circle 
    This is a Square
    

    这是因为draw()是一个method requirement,这意味着当draw()被调用时,运行时会在元素的实际类型内搜索draw ()的实现,本例中是在Circle内和Square

    现在,如果我们不将draw 声明为方法要求,这意味着我们不会在协议声明中提及它

    protocol Shape {
    }
    

    那么编译器将不再使用动态调度,而是直接执行协议扩展中定义的实现。因此代码将打印:

    This is a Shape
    This is a Shape
    

    此外,如果我们将数组的一个元素向下转换为我们期望的类型,那么我们会得到重载行为。这将打印This is a Circle

    if let circle = shapes[0] as? Circle {
        circle.draw()
    }
    

    因为编译器现在能够分辨出shapes 的第一个元素是Circle,并且由于Circle 有一个draw() 方法,所以它会调用那个方法。

    这是 Swift 处理抽象类的方式:它为您提供了一种方式来指定您对符合该协议的类型的期望,同时允许这些方法的默认实现。

    【讨论】:

    • “不能很好地处理多态性”是什么意思?
    • 这对我来说似乎倒退了,虽然我不太明白。似乎在协议中声明函数会产生“静态”行为。在我的示例中,您只能获得struct A 的实现,无论您是否转换为协议。如果您不使用该功能,那么您将获得“动态行为” - 您可以强制转换为协议以获得默认实现。我并不反对(因为我真的不知道我在说什么)。这似乎违反直觉。
    • @RomanSausarnes 我在答案中添加了更多详细信息,如果更新的答案澄清了问题,请告诉我。
    • 谢谢。这是一个非常有用的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-05
    相关资源
    最近更新 更多