Protocol implémenté par une classe et une structure
Bonjour à tous !
Petite question du vendredi : Pouvons nous créer une implémentation par défaut spécifique aux classes et une autre spécifique aux structures ?
Exemple :
protocol Copyable {
init(_: Self)
func copy() -> Self
}
extension Copyable {
func copy() -> Self {
return init(self)
}
}
Contrairement au classes, sur les stuctures se sont les valeurs qui primes et non les références. De ce fait, nous aurions pu écrire le code suivant pour ces dernières :
func copy() -> Self {
return self
}
L'idée serait de mettre dans extension Copyable les deux implémentations par défaut.
Ceci vous parait il envisageable ? Si oui, comment faire.
Je vous remercie par avance pour vos réponses.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
On peut faire quelque chose qui s'en rapproche sans forcément être très précis. Du moins à ma connaissance.
On aurait quelque chose semblable à ça :
AnyObject est un protocol auquel toutes les classes se conforment.
Toutefois pour le protocol Any, il représente tous les types :
Donc c'est pas exactement ce que tu demandes mais c'est quelque chose qui s'en rapproche.
À noter que tu peux explicitement dire que ton protocol peut être implémenter seulement par des class de cette façon.
C'est très utile pour optimiser la compilation d'informer le compilateur que seules des class peuvent implémenter le protocole. C'est d'ailleurs vrai pour le choix des structures, enum ou class ou de l'encapsulation, des extensions, des propriétés et autres. ça aide à savoir quelle méthode de Dispatch il va utiliser et donc sa performance.
Ah bien ! ça me va.
Même si ce n'est pas exactement ce que je veux, ta proposition a le mérite d'apporter une bonne réponse à ce que je cherche à faire.
Existe t'il un mécanisme similaire pour les structures ?
Il n'y a pas de différence quand tu copies un classe ou une struct.
Mais si tu veux utiliser ce code dans une struct :
... il ne revoie que la même référence. Ce n'est que sur la modification d'une struct que la référence change.
Et, si tu utilises ton protocol avec une classe :
... tu auras une erreur : Method 'copy()' in non-final class 'TestClass' must return `Self` to conform to protocol 'Copyable'. Du coup, tu devrais soit marquer la classe comme final soit ajouter la méthode suivante dans la classe :
Mais, pour la struct, c'est plus facile :
Si tu ajoutes le code ci-dessus dans une extension prévue pour les classes, ça passe ? Sinon en jouant sur les allias ça peut passer ?
L'idée est de ne pas avoir à dupliquer le code dans toutes les classes... ::)
Non, le code dans la méthode ne change rien. C'est la signature de la méthode qui renvoie Self qui est le problème.
C'est parce que, si tu ne marques pas la classe comme final, le compilateur ne peut pas déterminer le type de Self parce que il serait possible d'hériter de la classe ; ce qui changerais le type renvoyé.
Côté Copyable, il sera toujours le cas que tu doives écrire le "copy constructor" pour chaque classe parce que les données qu'il faut copier seront différents.
Si tu n'as pas d'hiérarchie de classes, c'est plus facile de les marquer comme final est le code dans l'extension ira pour toutes les classes.
Mais si tu crées un allias que tu le définis dans ta classe (implémentation du protocol) il saura forcément de quel type on parle, non ?
Je ne comprend pas ce que tu dis. Tu peux montrer du code ?
En même temps, peut-être j'ai réussi d'éviter le problème avec les classes non-final. C'était qqch comme ci que tu pensais ?
Oui je pensais à ça ou un truc du genre :
Mais, comme je t'ai déjà dit :
... ne crée pas une copie.
Oui oui oui mais dans la plupart des cas (vu qu'une structure n'est pas mutable) le code ci-dessous suffit. Sinon effectivement, il est nécessaire de créer une instance à partir du constructeur par défaut.
Pour toi, le code ci-dessous fonctionne pour les classes (sans redéfinition dans la classe) ?
Ce que j'ai fait avec le associatedtype est le seul moyen que j'ai encore trouvé qui réussit.
ça arrive parce que Any est valable comme type de base pour les classes, comme pour tous les types.
Mais, si je fais ce :
... car la classe n'est pas encore marqué comme final
Mais, si tu n'insistais pas à différencier le comportement de faire une copie entre les classes et les structs, le code suivant serait plus simple et va bien :
... mais à condition que tu marques les classes comme final. Sinon, il faut déclarer le associatedtype dans le protocol
C'est complètement débile que l'extension ne prenne pas en compte le type courant de l'objet... >:(
Plus simple et qui doit fonctionner pour les classes et structures (pas encore tester) mais on perd le typage :
Mais ça vol contre l'esprit de Swift. Le typage, c'est surtout ::)
En même temps Apple ne se gêne pas d'en faire de même avec son protocol NSCopying... Puis vu qu'il n'existe pas de solution duper élégante sans faire du bricolage dans l'implémentation.
Je reviens sur ça :
Effectivement ce n'est pas une copie au sens strict du terme mais le comportement reste identique à une vraie copie. La preuve :
Donc si on peut se passer d'un traitement c'est tout aussi bien.
En définitive, voici ce que j'ai fait (merci à Magiic pour le tuyau) :
Est-ce que tu as essayé de compiler ton code ?
Même après correction, tu devrais essayer d'implementer le protocol avec une class et une struct ;
Et, en supplement, l'erreur propose la solution : Protocol requires initializer 'init' with type 'Self'; do you want to add a stub?
Même si tu ne veux pas utiliser le 'copy constructor' pour la struct, il le faut dans la struct pour que la struct conforme au protocole
Parce que j'e suis au bon coe“ur, voici une solution (définitive ?)
Il y a une dans le code que je t'ai filé (je l'ai fait de mémoire à la machine à café xd ) :
Sinon oui, j'ai essayé hier dans playground. J'ai créé une classe et une structure, j'ai fait joujou avec et je n'ai pas constaté de soucis. Je t'ai mis un résultat partiel plus haut (que je vais te remettre ici).
Et, pour avoir le typage :
Moi j'aurais plus fait comme ça (même si ta solution est pertinente et intelligente ) :
Du coup dans tes implémentations :
Alors effectivement, tu vas devoir ajouter dans les structures init(_: TestClass) pour rien. Mais je préfère grandement un protocol qui fonctionne pour les classes et structures plutôt un contrat dépouillé de tout où l'implémentation doit s'adapter à ses propres spécificités. C'est deux visions des choses qui peuvent s'opposer mais qui se défendent.
Mais non. Il y a, ici, deux intérêts.
1. Copyable
2. Initialisable
En disant que quelque chose est Copyable, on ne dit pas qu'il faut être Initialisable ; voir l'exemple d'une struct.
Du coup, il faut séparer ces deux intérêts dans deux protocoles.
Et, en plus, si tu regardes mon dernier message tu y verras la solution la plus complète.
Ah mais je suis entièrement d'accord avec toi. L'idée de les séparer est judicieuse dans la mesure où nous sommes sur deux éléments distinct. J'ai bien lu ta proposition, elle est cohérente.
Pourquoi je dis que le protocol Copyable dépend du protocol Initialisable ? C'est simple, pour que le premier puisse fonctionner (tel que je l'ai conçu) il a besoin du second. Or ce n'est pas un concept complètement folklorique de créer une telle dépendance entre deux éléments distincts. Pour preuve, le protocol BinaryFloatingPoint dépend du protocol FloatingPoint qui lui même dépend de Comparable. Or si tu crées ton propres type de nombres décimaux, en suivant ta logique, tu devras écrire :
Si tu souhaites rester dans une certaine norme sans te dire "j'ai besoin de quoi pour que mon type suive la même logique que les autres nombre décimaux", par le jeu de dépendance, le code suivant suffit :
Moi je vois le truc comme ça : "pour faire une copie, j'ai besoin de ça".
Du coup par le même raisonnement, le code suivant suffira :
L'idée est d'éviter de pisser du code qui peut être factorisé (et donc de limiter les bugs).
Dis moi ce que tu en penses.
Il y a les protocoles qui dépendent sur les autres, bien sûr. Mais, dans ce cas là , Tu avais déterminer que l'on puisse copier une struct sans besoin d'un initialiseur. Du coup, deux intérêts - deux protocoles 8--)
La pointe quand il faut "mélanger" les deux, c'est sur l'extension pour AnyObject, pour que tous les classes qui implémentent Copyable puissent être CopyInitialisable aussi.
Mais, tu notes que j'ai fait hériter CopyInitialisable de AnyObject car c'est évident que tous qui soient CopyInitialisable doivent AnyObject au même temps.
C'est super ce que tu fais Jérémy mais n'oublie pas YAGNI (plus court et imagé : YAGNI) .
J'aime beaucoup ta proposition et je la trouve élégante. Il y a rien à redire.
Ce qui m'embête c'est ça :
Redéclarer ce protocol dans la classe ne me plait pas des masses mais faute de mieux, ça reste la solution la plus propre.
Oui j'étais passé à côté de ce détail, je n'avais pas vu ton "edit". ::)
Le but c'est de pouvoir créer des algorithmes qui fonctionnent aussi bien avec des structures (que nous aurions passées en paramètre) que des classes.
Enfin il y a quand même un souci si j'oublie dans la classe TestClass d préciser qu'elle répond au protocol CopyInitialisable, ce ne sera pas la bonne méthode copy() qui sera appelé.
1 - Le compilateur ne sera pas en mesure d'identifier le souci
2 - La méthode appelée ne fera pas de copie mais retournera l'objet courant
Mais bon... Tel est le quotidien des devs : Optimisation VS Maintenabilité
Je ne redéclare pas le protocol dans la classe, je dit à la classe qu'elle doit conformer à ce protocol.
Là , tu pourrais trouver des soucis ; plus que tu puisse imaginer à ce moment ???
Mais bien sûr ! Si tu ne dit pas à la classe qu'elle conforme au protocol qui déclare le version de copy(_: ), le compilateur passe, par défaut, à la méthode la plus proche, qui est la méthode pour Self : Any, qui renvoie le même objet et pas la copie.
Du coup, comme tu as trouvé, il faut suivre les conseilles de la petite nounours 8--)
Tu peux applaudir maintenant
Je te présente mes excuses, c'est une erreur de vocabulaire de mon côté.
Ah bon ? Tu peux m'en dire plus ? ???
Mais je n'ai jamais dit que ta solution était pourrit ! Bien au contraire. Je dis simplement que si le protocol Copyable dépendait de CopyInitialisable, on éviterait des oublies mais comme tu l'as mentionné, nous serions obligé de de créer la méthode init(_: Self) dans les structures qui en dépendent même si ça ne sert à rien. ::)