协议“行”只能用作通用约束,因为它具有“自身”或关联的类型要求

时间:2019-03-08 15:13:56

标签: ios swift

我正在迅速处理协议。我以为它将类似于其他语言中的“接口”。我正在测试它将如何与变量一起使用。协议对我来说是很新的,因为我从未见过带有非静态变量的接口。我创建了一个站协议。

protocol Station{
    var id:String {get set}
    var name:String {get set} // station name
    var lines: Array<String> {get set} // all lines persent in this station 
}

然后包含该站参考的行。它还包括可哈希协议。

protocol Line: Hashable  {
    var lineId: String{get set}
    var station1:Station {get set}    // start station
    var station2:Station {get set}   // end station
    var stations:Array<Station> {get set}   // all the stations that is in this line   
}

这是该协议的实现

struct LinesImpl: Line{
    var station1: Station
    var station2: Station
    var stations: Array<Station>
    var lineId: String
    static func == (lhs: LinesImpl, rhs: LinesImpl) -> Bool {
        return (lhs.station1.name == rhs.station1.name && lhs.station2.name == rhs.station2.name && lhs.lineId == rhs.lineId)
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(station1.name)
        hasher.combine(station2.name)
        hasher.combine(lineId)
    }
    init(station1: Station, station2: Station, stations: [Station], lineId: String){
        self.station1 = station1
        self.station2 = station2
        self.stations = stations
        self.lineId = lineId   
    }
}

一旦我添加了Hashable。我不再能够为Line协议创建引用。

当我尝试这样做时,它说“协议'Line'只能用作通用约束,因为它具有Self或关联的类型要求”:

enter image description here

我在这里看到了很多类似的问题,但是我仍然看不到错误是什么。任何帮助都是极好的。谢谢你们。

1 个答案:

答案 0 :(得分:4)

添加Hashable时,添加了Equatable,并且由于Equatable是PAT,因此使它成为具有关联类型(PAT)的协议。 PAT不是一种类型。这是用于将方法添加到其他类型的工具。

不能将PAT用作变量的类型,不能将其放入数组中,不能直接将其作为参数传递,也不能将其作为值返回。 PAT的唯一要点是作为通用约束(where L: Line)。 PAT指出了另一个具体类型必须提供的内容,以便在某些情况下可用。

该如何解决尚不清楚。这看起来似乎根本不是协议。这取决于您要在此处解决的代码重用问题。

协议通常是关于可以做什么的内容。 Line似乎只是在试图隐藏实现,而不表达任何内容。那不是协议。如所写,这里根本没有理由使用泛型或协议。 Line的其他实现是什么样的? (我很难想象您还能如何实现这种类型。)

我怀疑正确的答案是将所有这些替换为Station结构和Line结构。我没有看到协议在哪里发挥作用。

这是一种我可以实现您正在构建的内容的方法,以及一些新协议以查看它们的用途。其中一些可能比您需要解决的更多,但我想展示实际的协议。

// A protocol for "ID" types that automatically gives them handy inits
// Nested ID types mean that Line.ID can't get confused with Station.ID.
// The point of a protocol is to add additional features to a type like this.
protocol IDType: Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
    var value: String { get }
    init(value: String)
}

extension IDType {
    // For convenience
    init(_ value: String) { self.init(value: value) }
    // Default impl for ExpressibleByStringLiteral
    init(stringLiteral value: String) { self.init(value: value) }
    // Default impl for CustomStringConvertible
    var description: String { return value }
}

struct Line: Equatable  {
    struct ID: IDType { let value: String }
    let id: ID
    let stations: [Station]
    var origin: Station { return stations.first! } // We ensure during init that stations is non-empty
    var terminus: Station { return stations.last! }

    init(id: ID, origin: Station, stops: [Station], terminus: Station) {
        self.id = id
        self.stations = [origin] + stops + [terminus]
    }
}

// Conforming Line to this protocol lets it print more beautifully.
extension Line: CustomStringConvertible {
    var description: String { return "\(id): \(origin) -> \(terminus)" }
}

// Stations can't contain Line directly. These are value types, and that would be
// recursive. But this is nice because it lets us construct immutable Stations
// and then glue them together with Lines which may even be in independent
// systems (for example, the bus system might be separate from the rail system,
// but share stations)
struct Station: Hashable {
    struct ID: IDType { let value: String }
    let id: ID
    let name: String

    func lines(in system: System) -> [Line] {
        return system.linesVisiting(station: self)
    }
}

extension Station: CustomStringConvertible {
    var description: String { return name }
}

struct System: Equatable {
    let lines: [Line]

    // Using Set here makes it clear there are no duplicates, and saves
    // some hassle turning it back into an Array, but we could also just
    // return Array here as Array(Set(...))
    var stations: Set<Station> {
        // Uniquify the stations
        return Set(lines.flatMap { $0.stations })
    }

    func linesVisiting(station: Station) -> [Line] {
        return lines.filter { $0.stations.contains(station) }
    }
}

// Some examples of using it.
let stationNames = ["Shady Grove", "Bethesda", "Metro Center", "Glenmont",
                    "Wiehle-Reston East", "Largo Town Center"]

// Build up a few stations; obviously there are many more
let stations = Dictionary(uniqueKeysWithValues:
    stationNames.map { ($0, Station(id: .init($0), name: $0)) })

// Define some lines
let redLine = Line(id: "OR",
                   origin: stations["Shady Grove"]!,
                   stops: [stations["Bethesda"]!, stations["Metro Center"]!],
                   terminus: stations["Glenmont"]!)

let silverLine = Line(id: "SV",
                      origin: stations["Wiehle-Reston East"]!,
                      stops: [stations["Metro Center"]!],
                      terminus: stations["Largo Town Center"]!)

// And glue them together into a system
let system = System(lines: [redLine, silverLine])