在 Swift 中执行协议的默认实现时,实现默认属性的正确方法是什么?

What's the correct way to implement default properties when doing default implementation of protocols in Swift?

我对 Swift 中的面向协议编程的概念非常着迷,因此,我正在将去年创建的一个旧项目(最初是一个 OOP 框架)迁移到 POP .

在这个阶段,我遇到的问题可能是因为我对 POP 的理解不正确,或者 Swift 2.0 Betas 没有创建真正面向协议的框架的所有内容(不是很有可能 - 如果有的话,我可能会误解 POP 的某些方面)。

Protocol-Oriented Programming 是不到一个月前引入世界的全新编程范式,因此关于它的书面内容并不多(only tutorial 我在该主题上找到的没有解决我遇到的问题,WWDC 视频也没有)。

总之,直截了当地说:要么我在这里做错了什么,要么面向协议编程的缺点之一就是您一定会重复很多代码。例证:

我有以下协议,它具有许多属性并且也符合 Equatable 协议:

protocol MediaType : Equatable {

    /// MARK: - Properties

    /// The ID of the media, as assigned by MyAnimeList.
    var ID: Int { get }

    /// The title of the media.
    var title: String { get }

    /// Other titles by which this anime may be commonly known (only available in details requests).
    var otherTitles: (synonyms: [String], english: [String], japanese: [String])? { get }

    /// The global ranking of the title (only available in anime details requests).
    var rank: Int? { get }

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    var popularityRank: Int? { get }

    /// URL to a representative image of the title. Usually a "cover" image.
    var imageURL: String { get }

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    var adaptations: Relationships { get }

    /// The user's rating of the media.
    var memberScore: Float { get }

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    var membersCount: Int? { get }

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    var favoritedCount: Int? { get }

    /// A short HTML-formatted description of the media.
    var synopsis: String { get }

    /// A list of genres for this title (only available in details requests).
    var genres: [String]? { get }

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    var tags: [String] { get }
}

在我的框架的原始版本中,这个协议是一个名为 Media 的 class,另外两个 class 继承自它。所以他们免费获得了所有这些财产。

但我似乎无法为遵守该协议的对象提供这些属性的默认实现(即 getter)?

我尝试的第一件事是简单地将协议提供给我的结构声明,但失败了,这是意料之中的,因为我没有为属性提供任何实现:

struct Anime : MediaType {

    /// MARK: - MediaType

}

/// Compares two Anime_ objects. Two Anime_ objects are considered equal when they have the same ID and title.
func ==(lhs: Anime, rhs: Anime) -> Bool {
    return (lhs.ID == rhs.ID) && (lhs.title == rhs.title)
}

这失败了:

Type 'Anime' does not conform to protocol 'MediaType'

我的下一个尝试是为 MediaType 编写一个扩展,并将属性放在那里:

extension MediaType {
    /// The ID of the media, as assigned by MyAnimeList.
    let ID: Int

    /// The title of the media.
    let title: String

    /// Other titles by which this anime may be commonly known (only available in details requests).
    let otherTitles: (synonyms: [String], english: [String], japanese: [String])?

    /// The global ranking of the title (only available in anime details requests).
    let rank: Int?

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    let popularityRank: Int?

    /// URL to a representative image of the title. Usually a "cover" image.
    let imageURL: String

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    let adaptations: Relationships

    /// The user's rating of the media.
    let memberScore: Float

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    let membersCount: Int?

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    let favoritedCount: Int?

    /// A short HTML-formatted description of the media.
    let synopsis: String

    /// A list of genres for this title (only available in details requests).
    let genres: [String]?

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    let tags: [String]
}

这没有用:

Extensions may not contain stored properties.

它有一个我非常不喜欢的缺点:我已经在复制代码,将协议的属性复制到扩展中。

所以最后,我永远无法让我的属性 "spread" 符合协议的对象,所以我最终将属性添加到 Anime 结构。

struct Anime : MediaType {

    /// MARK: - MediaType

    /// The ID of the media, as assigned by MyAnimeList.
    let ID: Int

    /// The title of the media.
    let title: String

    /// Other titles by which this anime may be commonly known (only available in details requests).
    let otherTitles: (synonyms: [String], english: [String], japanese: [String])?

    /// The global ranking of the title (only available in anime details requests).
    let rank: Int?

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    let popularityRank: Int?

    /// URL to a representative image of the title. Usually a "cover" image.
    let imageURL: String

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    let adaptations: Relationships

    /// The user's rating of the media.
    let memberScore: Float

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    let membersCount: Int?

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    let favoritedCount: Int?

    /// A short HTML-formatted description of the media.
    let synopsis: String

    /// A list of genres for this title (only available in details requests).
    let genres: [String]?

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    let tags: [String]

    /// MARK: - Anime


}

这似乎奏效了。但现在我在 MediaTypeAnime 中都有我的属性。在 OOP 中,您可以通过 subclassing.

避免重复代码

所以我在这里重复我的问题:我是不是误解了面向协议的编程,或者 POP 的缺点是您必须复制并粘贴您的协议特定逻辑每当您使 struct/class/enum 符合是吗?

那是因为您的 Anime 结构没有实现 MediaType 协议的所有属性。这是您如何操作的最小化示例:

protocol MediaType : Equatable {
    var ID: Int { get }
    var title: String { get }
}

struct Anime : MediaType {
    // Implement the MediaType protocol
    var ID : Int
    var title : String
}

/// Compares two Anime_ objects. Two Anime_ objects are considered equal when they have the same ID and title.
func ==(lhs: Anime, rhs: Anime) -> Bool {
    return (lhs.ID == rhs.ID) && (lhs.title == rhs.title)
}

let x = Anime(ID: 1, title: "title 1")
let y = Anime(ID: 2, title: "title 2")
let z = Anime(ID: 1, title: "title 1")

println(x == y) // false
println(x == z) // true

但有一点好奇:在协议中,为什么要将所有属性声明为只读?

我发布这篇文章已经几周了,但我相信 Aaron Brager 所说的是真的。

虽然面向协议的编程本身相当新,但协议的思想在 Objective-C 中已经存在很长时间了,它们在 Swift 中,并且它们在Java 等语言。由于协议和扩展的性质,似乎无法对属性进行默认实现,因为扩展不允许您在其中设置非计算属性。