簡體   English   中英

如何強制使用協議默認擴展“覆蓋”?

[英]How to force usage of protocol default extension "override"?

我有一個帶有 PAT 的協議,它在擴展中提供了默認實現。 然后,符合該協議的 class 提供“覆蓋”。 不調用此覆蓋,編譯器“首選”默認值。

//---- Definition of the protocol with a default implentation, because I am forced to.

protocol Bag: AnyObject {
    associatedtype BagObject
    
    func add(_ e: BagObject)
}

extension Bag where BagObject: Equatable {
    //    I here give a default implementation, because I can't do differently, as this
    //    is an extension, and it is an extension because it only applies to assoicated
    //    types that are Equatables
    func contains(_ e: BagObject) -> Bool { 
        print("Default implementation is called")
        return false 
    }
}

///---- Definition of a class that gives a concrete implementation of the protocol

class BagConcreteImplementation<BagObject>: Bag {
    var bag = Array<BagObject>()
    func add(_ e: BagObject) { bag.append(e) }
}

extension BagConcreteImplementation where BagObject: Equatable {
    //    I here give a concrete implementation when the generic type is Equatable
    func contains(_ e: BagObject) -> Bool { 
        print("Concrete implementation is called")
        return bag.contains(e) 
    }
}



///---- This is a class that encapsulate a bag, in real life, this class is adding some filtering to objects that can be added to the bag

class AClassThatHaveABag<BagType: Bag> {
    typealias BagObject = BagType.BagObject
    
    let myBag: BagType
    init(bag: BagType) { myBag = bag }
}

extension AClassThatHaveABag where BagType.BagObject: Equatable {
    func contains(_ e: BagObject) {
        //    The problem here is that the compiler only knows that myBag is a Bag
        // of Int and therefore calls the default implementation
        //    It does not call the concrete implementation of the concrete class that WILL be provided
        myBag.contains(e)
    }
}



let aBagOfInt    = BagConcreteImplementation<Int>()
let objectThatContainsABagOfInt = AClassThatHaveABag(bag: aBagOfInt)

aBagOfInt.contains(0)
objectThatContainsABagOfInt.contains(0)

// Prints
// Concrete implementation is called
// Default implementation is called

我們可以清楚地看到直接調用是調用具體實現,而封裝調用是調用默認實現。

即使通過封裝,如何確保始終調用具體實現?

我嘗試了一些事情,不知何故這有效:

在協議中聲明contains ,而不是在擴展中,如下所示:

func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject

如果您在 class 中執行T == BagObject類的操作,則會出現編譯器錯誤,指出這會使T變得多余,但顯然這在協議中是可以的。

然后,像這樣實現默認實現:

extension Bag {
    func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject {
        print("Default implementation is called")
        return false
    }
}

然后,您可以直接在 class 中實現具體實現,作為非泛型方法

class BagConcreteImplementation<BagObject> : Bag {
    func contains(_ e: BagObject) -> Bool where BagObject : Equatable {
        print("Concrete implementation is called")
        return bag.contains(e)
    }
    
    var bag = Array<BagObject>()
    func add(_ e: BagObject) { bag.append(e) }
}

在不更改調用站點的情況下,代碼將打印:

Concrete implementation is called
Concrete implementation is called

為了完整起見,我在這里發布了另一個解決方案,它避免使用我發現(我的拙見)不那么干凈的T == BagObject

這個想法是使用Bag的專用版本,稱為BagOfEquatables ,並擴展類

protocol Bag: AnyObject {
    associatedtype BagObject
    func add(_ e: BagObject)
}

// Specialized version of Bag for Equatables
protocol BagOfEquatables: Bag where BagObject: Equatable{
    func contains(_ e: BagObject) -> Bool
}

extension BagOfEquatables {
    func contains(_ e: BagObject) -> Bool {
        print("Default implementation is called")
        return false
    }
}

///---- Definition of a class that gives a concrete implementation of the protocol

class BagConcreteImplementation<BagObject>: Bag {
    var bag = Array<BagObject>()
    func add(_ e: BagObject) { bag.append(e) }
}

extension BagConcreteImplementation: BagOfEquatables where BagObject: Equatable {
    func contains(_ e: BagObject) -> Bool  {
        print("Concrete implementation is called")
        return bag.contains(e)
    }
}

///---- This is a class that encapsulate a bag

class AClassThatHaveABag<BagType: Bag> {
    typealias BagObject = BagType.BagObject
    let myBag: BagType
    init(bag: BagType) { myBag = bag }
}

extension AClassThatHaveABag where  BagType: BagOfEquatables {
    func contains(_ e: BagObject) -> Bool  {
        myBag.contains(e)
    }
}



let aBagOfInt    = BagConcreteImplementation<Int>()
let objectThatContainsABagOfInt = AClassThatHaveABag(bag: aBagOfInt)

aBagOfInt.contains(0)
objectThatContainsABagOfInt.contains(0)


// Prints
// Concrete implementation is called
// Concrete implementation is called

你說:

不調用此覆蓋,編譯器“首選”默認值。

問題是您在擴展中提供了該方法的默認實現,但從未將其聲明為協議本身的一部分。 結果,編譯器將使用“靜態調度”。

如果您在協議中包含該方法,編譯器將使用“動態調度”來解析要調用的方法。


例如:

protocol Foo {
    // without this declaration, this will use static dispatch;
    // uncomment the following line for dynamic dispatch

    // func foo()
}

extension Foo {
    func foo() {
        print("default")
    }
}

struct Bar: Foo {
    func foo() {
        print("bar implementation")
    }
}

let bar: Foo = Bar()
bar.foo()               // “default” with static dispatch; if you uncomment the line above for dynamic dispatch, it will change to ”bar implementation“

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM