Подтвердить что ты не робот

Могу ли я имитировать черты/миксины в Swift?

Есть ли у Swift способ смешивания в чертах, a la Scala? Раздел книги Swift по использованию расширений для добавления протоколов к существующим классам становится мучительно близким. Однако, поскольку протоколы не могут содержать реализацию, это не может использоваться для смешивания кода в класс. Есть ли другой способ?

4b9b3361

Ответ 1

С Swift 2.0, да!

Предоставление реализации по умолчанию

Вы можете использовать расширения протокола, чтобы обеспечить реализацию по умолчанию к любому методу или требованию свойства этого протокола. Если Соответствующий тип обеспечивает собственную реализацию требуемого метода или свойство, эта реализация будет использоваться вместо предоставляемый расширением.

Ответ 2

Одним из способов имитации микширования является использование общей функции для обеспечения реализации

Например, с помощью этих протоколов

protocol Named {
    func GetName() -> String
}

protocol NamedExtension {
    func GetLowercaseName() -> String
    func GetUppercaseName() -> String
}

Я хочу, чтобы какой-то класс реализовал GetName() и использовал микширование, чтобы они также получили GetLowercaseName() и GetUppercaseName() без их реализации

Это реализация NamedExtension как в свободной функции

func GetLowercaseNameImpl<T:Named>(obj:T) -> String {
    return obj.GetName().lowercaseString
}

func GetUppercaseNameImpl<T:Named>(obj:T) -> String {
    return obj.GetName().uppercaseString
}

и расширения на Int

extension Int : Named {
    func GetName() -> String {
        return "Int"
    }
}

extension Int : NamedExtension {
    // use provided implementation
    func GetLowercaseName() -> String {
        return GetLowercaseNameImpl(self)
    }
    func GetUppercaseName() -> String {
        return GetUppercaseNameImpl(self)
    }
}

и я могу использовать

1.GetName() // result Int
1.GetUppercaseName() // result "INT"
1.GetLowercaseName() // result "int"

Ответ 3

Я не знаю Scala, но из того, что вы говорите мне, можно одновременно создавать protocol и extension, которые расширяют тип, чтобы добавить поведение "псевдо-trait".

Например:

protocol IsGreaterThan
{
    func isGreaterThan(other:Int) -> Bool
    func isNotGreaterThan(other:Int) -> Bool
}

extension Int : IsGreaterThan
{
    func isGreaterThan(other:Int) -> Bool
    {
        return self > other
    }

    func isNotGreaterThan(other:Int) -> Bool
    {
        return !isGreaterThan(other)
    }
}

Настоящая подколенная сухожилия - это то, как дженерики сейчас несколько ограничены. Я думаю, что они улучшат многое в предстоящих версиях Swift.

Ответ 4

Как и Брайан Чен ответил:

import Foundation

protocol Named {
    var name : String { get }
}

protocol NamedExtension : Named { // NB extends Named
    var lowercaseName : String { get }
    var uppercaseName : String { get }
}

struct NamedExtensionDefault { // Put defaults inside a struct to keep name spaces seperate
    static func lowercaseName(named : NamedExtension) -> String {
        return (named.name as NSString).lowercaseString
    }
    static func uppercaseName(named : NamedExtension) -> String {
        return (named.name as NSString).uppercaseString
    }
}

extension Int : NamedExtension {
    var name : String {
    return "Int"
    }
    // Use default implementation
    var lowercaseName : String {
    return NamedExtensionDefault.lowercaseName(self)
    }
    var uppercaseName : String {
    return NamedExtensionDefault.uppercaseName(self)
    }
}

1.name // result Int
1.uppercaseName // result "INT"
1.lowercaseName // result "int"

Основное отличие от ответа Брайана заключается в том, что я не использовал generics, потому что сделал NamedExtension extends Named, так что реализации по умолчанию могут получить доступ к name.

Ответ 5

Здесь мой (еще не проверенный) способ сделать то, что я считаю Scala чертами в Swift 2.1.1, готовыми к игре, двумя версиями:

Менее гибкий:

protocol BigBadProtocol {
    func madFunc() -> String;
    // func otherFunc();
    // Maybe a couple more functions here.
}

protocol BlueMadFuncUser: BigBadProtocol {}

extension BlueMadFuncUser {
    func madFunc() -> String {
        return "Blue"
    }
}

protocol RedMadFuncUser: BigBadProtocol {}

extension RedMadFuncUser {
    func madFunc() -> String {
        return "Red"
    }
}

class ClearClass: BigBadProtocol {
    func madFunc() -> String {
        return "Clear"
    }
}

class BlueClass: BlueMadFuncUser {}

class RedClass: RedMadFuncUser {}

Более гибкий:

protocol BigBadProtocol {
    func madFunc() -> String;
    // func otherFunc();
    // Maybe a couple more functions here.
}

protocol BlueMadFuncUser {}

extension BigBadProtocol where Self: BlueMadFuncUser {
    func madFunc() -> String {
        return "Blue"
    }
}

protocol RedMadFuncUser {}

extension BigBadProtocol where Self: RedMadFuncUser {
    func madFunc() -> String {
        return "Red"
    }
}

class ClearClass: BigBadProtocol {
    func madFunc() -> String {
        return "Clear"
    }
}

class BlueClass: BigBadProtocol, BlueMadFuncUser {}

class RedClass: BigBadProtocol, RedMadFuncUser {}

Проверка работоспособности:

var classes: [BigBadProtocol] = [ClearClass(), BlueClass(), RedClass()]

// Prints "Clear, Blue, Red\n"
print((classes.map { $0.madFunc() }).joinWithSeparator(", "))

// Print another way for Playgrounds, which appears to bug out on the lines above
var s = ""
for klass in classes {
    s += klass.madFunc() + " "
}
print(s)

BlueMadFuncUser и RedMadFuncUser - это две версии признака. Моя терминология может быть выключена, но тогда вы можете самостоятельно создать вторую черту, подобную этой, и смешать и сопоставить в своих классах, как вам будет угодно.

Было бы гораздо более сложной задачей или использовать котельную-плиту-y для повторного использования логики с использованием подхода на основе наследования.

В итоге я захотел получить этот шаблон после того, как нашел его очень полезным в Hack for PHP, где из того, что я могу сказать, черты очень похожи на Scala: https://docs.hhvm.com/hack/other-features/trait-and-interface-requirements)