簡體   English   中英

在具有自我類型要求的協議上鍵入擦除

[英]Type erasure on protocol with Self type requirement

我有一個名為Shape的協議符合Comparable 最終我想創建一個符合此協議的數組,即使它們不是同一個子類型。

我創建了一些符合Shape類,即TriangleSquareRectangle 我想要做的是定義另一個名為Drawing類,它可以接受任何Shape的數組。

//: Playground - noun: a place where people can play
import Foundation
import UIKit

protocol Shape: Comparable {
    var area: Double { get }
}

extension Shape {
    static func < (lhs: Self, rhs: Self) -> Bool {
        return lhs.area < rhs.area
    }

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

class Triangle: Shape {
    let base: Double
    let height: Double

    var area: Double { get { return base * height / 2 } }

    init(base: Double, height: Double) {
        self.base = base
        self.height = height
    }
}

class Rectangle: Shape {
    let firstSide: Double
    let secondSide: Double

    var area: Double { get { return firstSide * secondSide } }

    init(firstSide: Double, secondSide: Double) {
        self.firstSide = firstSide
        self.secondSide = secondSide
    }
}

class Square: Rectangle {
    init(side:  Double) {
        super.init(firstSide: side, secondSide: side)
    }
}

class Drawing {
    //Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements
    let shapes: [Shape]
    init(shapes: [Shape]) {
        self.shapes = shapes
    }
}

但是,當我嘗試使用Shape作為數組的類型時,我得到以下錯誤Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements 如何聲明包含任何類型形狀的數組?

你已經解決了設計協議中可能遇到的每一個基本錯誤,但它們都是極其常見的錯誤並不奇怪。 當他們開始時,每個人都這樣做,並且需要一段時間才能讓你的頭腦在正確的空間里。 我一直在思考這個問題差不多四年,並給會談關於這個問題,我還是搞砸了,並有備份和重新設計我的協議,因為我陷入了同樣的錯誤。

協議不是抽象類的替代品。 有兩種完全不同的協議:簡單協議和PAT(具有相關類型的協議)。

簡單的協議代表一個具體的接口,可以在某些方法中使用,在其他語言中使用抽象類,但最好將其視為需求列表。 也就是說,你可以把一個簡單的協議想象成一個類型(它實際上變成了一個存在主義,但它非常接近)。

PAT是一種用於約束其他類型的工具,以便您可以為這些類型提供其他方法,或將它們傳遞給通用算法。 但PAT不是一種類型。 它不能放在一個數組中。 它無法傳遞給函數。 它不能保存在變量中。 它不是一種類型。 沒有“可比的”這樣的東西。 有些類型符合 Comparable。

可以使用類型擦除器強制PAT成為具體類型,但它幾乎總是一個錯誤而且非常不靈活,如果你必須發明一種新型橡皮擦來做這件事,那就特別糟糕了。 作為一項規則(也有例外),假設如果您要使用類型橡皮擦,您可能錯誤地設計了協議。

當你制作Comparable(並通過它Equatable)一個Shape的要求時,你說Shape是一個PAT。 你不想那樣。 但話說回來,你不想要Shape。

很難確切地知道如何設計它,因為您沒有顯示任何用例。 協議來自用例。 它們通常不會從模型中涌現出來。 所以我將提供你如何開始,然后我們可以討論如何根據你將用它做什么來實現更多的部分。

首先,您可以將這些形狀建模為值類型。 他們只是數據。 參考語義(類)沒有理由。

struct Triangle: Equatable {
    var base: Double
    var height: Double
}

struct Rectangle: Equatable {
    var firstSide: Double
    var secondSide: Double
}

我刪除了Square,因為這是一個非常糟糕的例子。 在繼承模型中,正方形不是正確的矩形(請參閱圓橢圓問題 )。 你碰巧使用不可變數據來逃避它,但不可變數據並不是Swift的常態。

看起來你想在這些上計算面積,所以我假設有一些關心它的算法。 它可以用於“提供區域的區域”。

protocol Region {
    var area: Double { get }
}

我們可以說三角形和矩形通過追溯建模符合Region。 這可以在任何地方完成; 在創建模型時不必決定它。

extension Triangle: Region {
    var area: Double { get { return base * height / 2 } }
}

extension Rectangle: Region {
    var area: Double { get { return firstSide * secondSide } }
}

現在Region是一個簡單的協議,所以把它放在一個數組中是沒有問題的:

struct Drawing {
    var areas: [Region]
}

這留下了原始的平等問題。 這有很多細微之處。 第一個也是最重要的是,在Swift中“等於”(至少在與Equatable協議相關時)意味着“可以替代任何目的”。 因此,如果你說“triangle == rectangle”你必須指“在任何情況下都可以使用這個三角形,你可以自由地使用矩形。” 它們恰好具有相同區域的事實似乎不是定義該替換的非常有用的方式。

同樣地說“三角形小於矩形”也沒有意義。 有意義的是說三角形的面積小於一個矩形,但這只是意味着Area的類型符合Comparable ,而不是形狀本身。 (在您的示例中, Area等於Double 。)

肯定有各種方法可以推進並測試各地區之間的平等(或類似平等),但這在很大程度上取決於您計划如何使用它。 它不會從模型中自然地彈出; 這取決於你的用例。 Swift的強大之處在於它允許相同的模型對象符合許多不同的協議,支持許多不同的用例。

如果你能給出一些關於你在這個例子中的更多指針(調用代碼的樣子),那么我可以擴展它。 特別是,通過梳理開始Drawing一點點。 如果你從不訪問數組,那么你輸入的內容並不重要。 將理想什么for循環看起來像在這陣?

您正在處理的示例幾乎就是最着名的面向協議的編程演講中使用的示例: Swift中的面向協議編程 ,也稱為“Crusty talk”。 這是開始理解如何在Swift中思考的好地方。 我相信它會引發更多問題。

暫無
暫無

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

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