Есть ли у Swift способ смешивания в чертах, a la Scala? Раздел книги Swift по использованию расширений для добавления протоколов к существующим классам становится мучительно близким. Однако, поскольку протоколы не могут содержать реализацию, это не может использоваться для смешивания кода в класс. Есть ли другой способ?
Могу ли я имитировать черты/миксины в Swift?
Ответ 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)