¿Puedo simular rasgos/mixins en Swift?
traits (5)
A partir de Swift 2.0, ¡sí!
Proporcionar implementaciones por defecto
Puede usar extensiones de protocolo para proporcionar una implementación predeterminada a cualquier método o requisito de propiedad de ese protocolo. Si un tipo conforme proporciona su propia implementación de un método o propiedad requerida, esa implementación se utilizará en lugar de la proporcionada por la extensión.
¿Tiene Swift una forma de mezclar rasgos, a la Scala? La sección del libro de Swift sobre el uso de extensiones para agregar protocolos a las clases existentes se acerca de manera tentadora. Sin embargo, como los protocolos no pueden contener una implementación, esto no se puede usar para mezclar código en una clase. ¿Hay otra manera?
Esta es mi forma (aún no probada) de hacer lo que creo que son los rasgos de Scala en Swift 2.1.1, Playgrounds-ready, dos versiones:
Menos flexible:
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 {}
Mas flexible:
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 {}
Prueba de cordura:
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 y RedMadFuncUser son dos versiones de un rasgo. Mi terminología puede estar desactivada, pero luego puedes crear de forma independiente un segundo rasgo como ese y mezclar y combinar en tus clases como quieras.
Sería mucho más desafiante o sería difícil reutilizar la lógica con un enfoque basado en la herencia.
Terminé queriendo este patrón después de encontrarlo muy útil en Hack para PHP, donde, por lo que puedo decir, los rasgos son muy similares a los de Scala: https://docs.hhvm.com/hack/other-features/trait-and-interface-requirements )
No conozco a Scala, pero por lo que me está diciendo, es posible crear simultáneamente un protocol
y una extension
que extienda un tipo para agregar un comportamiento de "pseudo rasgo".
Por ejemplo:
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)
}
}
El verdadero problema es cómo los genéricos están limitados por ahora. Creo que mejorarán mucho en las próximas revisiones de Swift.
Similar a la respuesta de Bryan Chen:
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"
La principal diferencia con la respuesta de Bryan es que no NamedExtension
genéricos porque hice NamedExtension
extiende Named
, de modo que las implementaciones predeterminadas puedan acceder a name
.
Una forma de simular la mezcla es usar la función genérica para proporcionar implementación
Por ejemplo con estos protocolos.
protocol Named {
func GetName() -> String
}
protocol NamedExtension {
func GetLowercaseName() -> String
func GetUppercaseName() -> String
}
Quiero que alguna clase implemente GetName()
y use la mezcla para que también obtengan GetLowercaseName()
y GetUppercaseName()
sin implementarlos
Esta es la implementación de NamedExtension
como en la función libre
func GetLowercaseNameImpl<T:Named>(obj:T) -> String {
return obj.GetName().lowercaseString
}
func GetUppercaseNameImpl<T:Named>(obj:T) -> String {
return obj.GetName().uppercaseString
}
y extensiones en 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)
}
}
y puedo usar
1.GetName() // result Int
1.GetUppercaseName() // result "INT"
1.GetLowercaseName() // result "int"