Каков правильный способ реализации свойств по умолчанию при реализации протоколов по умолчанию в Swift?

Я действительно очарован концепцией протокольно-ориентированного программирования в Swift, и поэтому я переношу старый проект, который я создал в прошлом году (который изначально был ООП-фреймворком), на POP.

На данном этапе проблемы, с которыми я сталкиваюсь, могут быть связаны с тем, что я либо неправильно понимаю POP, либо в бета-версиях Swift 2.0 нет всего, что нужно для создания действительно ориентированной на протокол структуры (маловероятно — если что-то я могу неправильно понять некоторые аспекты POP).

Протокольно-ориентированное программирование — это совершенно новая парадигма программирования, представленная миру менее месяца назад, поэтому о ней не так много письменного контента (единственное руководство, которое я обнаружил в этой теме, не решает проблему, с которой я столкнулся, как и видео 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 и два других класса, унаследованных от него. Таким образом, они получили все эти свойства бесплатно.

Но не похоже, что я могу дать своим объектам, которые соответствуют этому протоколу, реализацию по умолчанию (а именно, геттеры) для этих свойств?

Первое, что я попробовал, а именно просто указать протокол для объявления моей структуры, потерпело неудачу, чего и следовало ожидать, поскольку я не предоставлял никакой реализации для свойств:

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)
}

Это не удается с:

Тип «Аниме» не соответствует протоколу «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]
}

Это не сработало:

Расширения не могут содержать сохраненные свойства.

И у него был один недостаток, который мне очень не нравился: я уже дублировал код, копируя свойства протокола в расширение.

Так что, в конце концов, я так и не смог «распространить» свои свойства на объекты, соответствующие протоколу, поэтому в итоге я добавил свойства в структуру 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


}

И это, похоже, сработало. Но теперь у меня есть свойства и в MediaType, и в Anime. В ООП вы избегаете дублирования кода путем создания подклассов.

Итак, я повторяю свой вопрос здесь: я неправильно понимаю протокольно-ориентированное программирование, или это недостаток POP, который заключается в том, что вам приходится копировать и вставлять свою специфичную для протокола логику всякий раз, когда вы приводите структуру/класс/перечисление в соответствие с ней?


person Andy Ibanez    schedule 04.07.2015    source источник
comment
Вы можете использовать вычисляемые свойства в расширении протокола, но расширения не могут иметь обычные свойства. Это ограничение останется.   -  person Aaron Brager    schedule 05.07.2015
comment
Определенно похоже, что из-за этого ограничения не будет реализации свойств по умолчанию в протоколах. Интересно подумать об этом.   -  person Andy Ibanez    schedule 05.07.2015
comment
Я согласен. Я надеюсь, что расширения позволят хранить в будущем.   -  person Aaron Brager    schedule 05.07.2015
comment
В более общем смысле рассмотрите возможность разбить протокол MediaType на отдельные более мелкие протоколы, а затем добавить методы к расширениям этих протоколов. Сила POP заключается в сочетании модульности, использования типов значений и реализации методов по умолчанию.   -  person Aaron Brager    schedule 05.07.2015
comment
Аарон, не могли бы вы добавить свои комментарии в качестве ответа? В конечном счете, из-за отсутствия материала о POP, я думаю, что ваши комментарии — это ответ, и они определенно помогут людям, пока POP не станет более устоявшимся.   -  person Andy Ibanez    schedule 05.07.2015


Ответы (2)


Прошло несколько недель с тех пор, как я опубликовал это, но я верю, что то, что сказал Аарон Брагер, правда.

Хотя протокольно-ориентированное программирование само по себе является довольно новым, идея протоколов уже давно присутствует в Objective-C, они есть в Swift и имеют свои варианты в таких языках, как Java. Из-за природы протоколов и расширений кажется, что реализация свойств по умолчанию невозможна, поскольку расширения не позволяют вам устанавливать в них невычисляемые свойства.

person Andy Ibanez    schedule 24.07.2015

Это связано с тем, что ваша структура 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

Однако одно любопытство: почему в протоколе вы объявляете все свойства доступными только для чтения?

person Code Different    schedule 04.07.2015
comment
Да, это то, что я говорю. Значит, нет способа реализовать свойства по умолчанию в протоколах? -- Свойства доступны только для чтения, поскольку все содержимое возвращается с сервера. Это не то, что пользователь может редактировать. - person Andy Ibanez; 05.07.2015
comment
Ответ в том, что это сложно. Тип, соответствующий протоколу, должен реализовывать все свойства, объявленные протоколом. Тем не менее, для методов можно использовать несколько приемов: nshipster. com/swift-default-protocol-implementations - person Code Different; 05.07.2015