2017-02-09 103 views
8

我不認爲這可以做,但我會問無論如何。我有一個協議:Swift Equatable on protocol

protocol X {} 

和A類:

class Y:X {} 

在我的代碼的其餘部分我使用協議X中的代碼是指一切,我希望能夠做一些事情像:

let a:X = ... 
let b:X = ... 
if a == b {...} 

的問題是,如果我嘗試實施Equatable

protocol X: Equatable {} 
func ==(lhs:X, hrs:X) -> Bool { 
    if let l = lhs as? Y, let r = hrs as? Y { 
     return l.something == r.something 
    } 
    return false 
} 

想法試圖允許使用==,同時隱藏協議後面的實現。

斯威夫特不喜歡這個,雖然因爲EquatableSelf引用,它將不再允許我使用它作爲類型。只作爲一個通用的論點。

那麼有沒有人找到一種方法來將協議應用到協議,而協議不能作爲一種類型使用?

回答

1

您必須實施協議擴展約束到您的類類型。在該擴展中,您應該實現Equatable運算符。

public protocol Protocolable: class, Equatable 
{ 
    // Other stuff here... 
} 

public extension Protocolable where Self: TheClass 
{ 
    public static func ==(lhs: Self, rhs:Self) -> Bool 
    { 
     return lhs.name == rhs.name 
    } 
} 


public class TheClass: Protocolable 
{ 
    public var name: String 

    public init(named name: String) 
    { 
     self.name = name 
    } 
} 

let aClass: TheClass = TheClass(named: "Cars") 
let otherClass: TheClass = TheClass(named: "Wall-E") 

if aClass == otherClass 
{ 
    print("Equals") 
} 
else 
{ 
    print("Non Equals") 
} 

但是,讓我推薦您將運算符實現添加到您的類中。保持簡單;-)

+0

謝謝。我試圖避免約束一個協議擴展到一個類,因爲(也許這是一個壞主意,但它適用於我的情況)我有不同的協議實現,實際上指的是同樣的事情,因此我希望他們被認爲是平等的 – drekka

3

也許這將有助於你:

protocol X:Equatable { 
    var name: String {get set} 

} 

extension X { 
    static func ==(lhs: Self, rhs: Self) -> Bool { 
     return lhs.name == rhs.name 
    } 
} 

struct Test : X { 
    var name: String 
} 

let first = Test(name: "Test1") 
let second = Test(name: "Test2") 

print(first == second) // false 
+3

謝謝,但這個工作,直到你嘗試做一些像'let first = Test(name:「Test1」)爲X'那麼你得到了關於不允許'X'作爲類型的錯誤。問題在於我的類在API的深處,只能通過協議公開,所以這些實例的使用必須是協議類型 – drekka

3

爲什麼你應該三思而後有一協議符合Equatable的原因是,在許多情況下,它只是沒有感。考慮下面這個例子:

protocol Pet: Equatable { 
    var age: Int { get } 
} 

extension Pet { 
    static func == (lhs: Pet, rhs: Pet) -> Bool { 
    return lhs.age == rhs.age 
    } 
} 

struct Dog: Pet { 
    let age: Int 
    let favoriteFood: String 
} 

struct Cat: Pet { 
    let age: Int 
    let favoriteLitter: String 
} 

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza") 
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina") 

if rover == simba { 
    print("Should this be true??") 
} 

你大概鍵入的==實施內檢查,但問題是,你有沒有關於任何類型的信息超越他們是Pet S和你不知道所有的事情,可能是Pet(也許您稍後將添加BirdRabbit)。如果你真的需要這個,另一種方法可以模擬像C#語言中如何實現平等,這樣做是這樣的:

protocol IsEqual { 
    func isEqualTo(_ object: Any) -> Bool 
} 

protocol Pet: IsEqual { 
    var age: Int { get } 
} 

struct Dog: Pet { 
    let age: Int 
    let favoriteFood: String 

    func isEqualTo(_ object: Any) -> Bool { 
    guard let otherDog = object as? Dog else { return false } 

    return age == otherDog.age && favoriteFood == otherDog.favoriteFood 
    } 
} 

struct Cat: Pet { 
    let age: Int 
    let favoriteLitter: String 

    func isEqualTo(_ object: Any) -> Bool { 
    guard let otherCat = object as? Cat else { return false } 

    return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter 
    } 
} 

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza") 
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina") 

if !rover.isEqualTo(simba) { 
    print("That's more like it.") 
} 

在這一點,如果你真的想要的,你可以實現==沒有實現Equatable

static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) } 

這件事情你必須注意的一件事是繼承。因爲您可以下傳一個繼承類型並刪除可能使isEqualTo不符合邏輯意義的信息。

最好的方法是隻實現類/結構本身的平等,並使用另一種機制進行類型檢查。

+0

謝謝,我一直在考慮'isEqual'選項(來自Java),但是希望保持簡單,因爲我有一種情況,我有不同的類代表相同的情境事物,因此即使它們是不同的實現,我也會認爲它們是平等的。 – drekka

+0

在兩個不同的實現上實現相等性物體的類型是一個光滑的斜坡。我建議另一個選項來比較它們,也許通過將一種類型轉換爲另一種類型然後進行比較。 –

2

不知道爲什麼你需要協議的所有實例符合Equatable,但我更喜歡讓類實現它們的等式方法。

在這種情況下,我會離開該協議簡單:

protocol MyProtocol { 
    func doSomething() 
} 

如果您需要符合MyProtocol對象也Equatable你可以使用MyProtocol & Equatable類型約束:

// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable { 
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) { 
    if element1 == element2 { 
     element1.doSomething() 
    } 
} 

通過這種方式,您可以保持規範清晰,並讓子類僅在需要時才實現其等同方法。

2

我仍然建議不要使用多態性實施==。這是一種代碼味道。如果你想給框架用戶一些東西,他可以測試平等,那麼你應該真的出售struct,而不是。這並不是說,它不可能是 S中的自動售貨機,雖然在struct S:

struct Info: Equatable { 
    let a: Int 
    let b: String 

    static func == (lhs: Info, rhs: Info) -> Bool { 
    return lhs.a == rhs.a && lhs.b == rhs.b 
    } 
} 

protocol HasInfo { 
    var info: Info { get } 
} 

class FirstClass: HasInfo { 
    /* ... */ 
} 

class SecondClass: HasInfo { 
    /* ... */ 
} 

let x: HasInfo = FirstClass(/* ... */) 
let y: HasInfo = SecondClass(/* ... */) 

print(x == y) // nope 
print(x.info == y.info) // yep 

我覺得這更有效地傳達你的意圖,這基本上是「你中有這些事情,你不這樣做知道它們是否是相同的東西,但是您確實知道它們具有相同的一組屬性,您可以測試這些屬性是否相同。「這與我將如何實現Money示例非常接近。

3

如果您直接在協議上實現Equatable,它將不再可用作類型,這違背了使用協議的目的。即使您只是在沒有Equatable一致性的協議上實現==功能,結果也可能是錯誤的。請參見我的博客上這篇文章對這些問題的演示:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/

,我已經找到工作最好是使用類型擦除的方法。這允許對協議類型(包裝在類型擦除器中)進行==比較。重要的是要注意,雖然我們繼續在協議級別工作,但實際的==比較被委託給底層的具體類型以確保正確的結果。

我已經使用您的簡單示例構建了一個橡皮擦類型,並在最後添加了一些測試代碼。我爲協議添加了一個類型String的常量,並創建了兩個符合類型(結構對於演示目的來說是最容易的),以便能夠測試各種場景。

所使用的類型擦除方法的詳細說明,請查看上述博客文章的第二部分:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/

下面的代碼應該支持相等比較,你想實現。您只需將協議類型包裝在類型橡皮擦實例中。

protocol X { 
    var name: String { get } 
    func isEqualTo(_ other: X) -> Bool 
    func asEquatable() -> AnyEquatableX 
} 

extension X where Self: Equatable { 
    func isEqualTo(_ other: X) -> Bool { 
     guard let otherX = other as? Self else { return false } 
     return self == otherX 
    } 
    func asEquatable() -> AnyEquatableX { 
     return AnyEquatableX(self) 
    } 
} 

struct Y: X, Equatable { 
    let name: String 
    static func ==(lhs: Y, rhs: Y) -> Bool { 
     return lhs.name == rhs.name 
    } 
} 

struct Z: X, Equatable { 
    let name: String 
    static func ==(lhs: Z, rhs: Z) -> Bool { 
     return lhs.name == rhs.name 
    } 
} 

struct AnyEquatableX: X, Equatable { 
    var name: String { return value.name } 
    init(_ value: X) { self.value = value } 
    private let value: X 
    static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool { 
     return lhs.value.isEqualTo(rhs.value) 
    } 
} 

// instances typed as the protocol 
let y: X = Y(name: "My name") 
let z: X = Z(name: "My name") 
let equalY: X = Y(name: "My name") 
let unequalY: X = Y(name: "Your name") 

// equality tests 
print(y.asEquatable() == z.asEquatable())   // prints false 
print(y.asEquatable() == equalY.asEquatable())  // prints true 
print(y.asEquatable() == unequalY.asEquatable()) // prints false 

注意,由於類型橡皮擦符合協議,你可以使用橡皮擦型隨時隨地協議類型的實例預期的情況下。

希望這會有所幫助。