Singleton ou NSUserDirectory ou autre ?
mybofy
Membre
Bonjour.
Quel est le moyen le plus robuste pour passer un objet à un autre objet ?
Exemple :
J'ai un NSMenuItem "menu".
En fonction de ce qui se passe dans un objet, disons "plante", je veux enabler ou disabler "menu".
J'utilise un singleton.
Par exemple : [gbl.menu setEnabled:YES] dans "plante".
Est-ce un style de programmation correct en Cocoa ?
Quel serait le bon style ?
Merci.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Par exemple utiliser un singleton pour un objet vue est une mauvaise pratique. En général pour les objets modèle c'est également peu probable. Le seul cas où j'ai déjà vu et validé ou préconisé des sharedInstance (puisque de toute façon avec ARC on ne peut plus faire des singletons mais que des sharedInstance), c'est pour les objets métier.
Je prends aussi souvent l'exemple du jeu d'échecs : imagine que tu veuilles modéliser un jeu d'échecs, tu vas avoir un objet Partie, des objets Pieces, des objets Coups, des objets Joueurs. Pour l'instant ton application te présente un plateau, pour jouer une seule partie. Certes dans ton application tu n'as pour l'instant qu'un seul objet Partie donc. Mais de là à en faire un singleton, ce n'est pas une bonne idée pour autant. Car le jour où tu voudras que ton appli finalement puisse ouvrir plusieurs Parties dans des fenêtres différentes, ou pouvoir sauver des Parties et les réouvrir, etc, ... Bref, il n'y a pas de justification valable pour rendre cette classe Partie singleton.
Le seul cas où j'utilise des sharedInstance, c'est pour mes objets métiers de "Service", genre dans un projet ma ou mes classes qui encapsulent mes appels à mon ou mes WebServices. Ou des managers. Mais sinon en général ce n'est pas justifié.
C'est vrai que parfois on voit aussi l'utilisation d'un singleton pour regrouper toutes les variables dont on veut pouvoir accéder de n'importe où dans l'application. Un singleton avec autant de @proprerty qu'on a de variable qu'on veut garder accessible de partout. Mais en pratique cela cache en général un mauvais design.
(Et ces remarques ne sont pas propres à Cocoa, mais de l'architecture logicielle générale pour de la POO).
Par exemple dans ton cas, si ton objet "plante" doit désactiver le menu, c'est pas très logique de faire ça directement, cela crée un lien fort entre ton objet modèle "Plante" et ton menu, ton objet Plante sera loin d'être réutilisable dans une autre application ou si ton interface change et que tu as réorganisé les choses en n'utilisant plus des NSMenuItem mais des boutons à désactiver, et de toute façon c'est pas à ta plante de connaà®tre l'IHM de ton application pour savoir quoi désactiver, c'est un objet modèle, donc à décoreller de l'IHM !
Non, il faut plutôt :
- Dans ton Controller utiliser le KVO pour être informé quand ton objet modèle (la plante) change d'état, pour affecter la vue (ton IHM, ton NSMenuItem aujourd'hui mais peut-être un NSButton demain) en conséquence.
- Ou alors utiliser des NSNotifications pour laisser ton objet modèle Plante signaler à qui veut l'entendre que ta plante a changé, et laisser le Controlleur être à l'écoute de cette notif pour affecter l'IHM en conséquence
- Ou alors utiliser le pattern delegate
Bref, entre le pattern Observer, Notification et Delegate, tu as le choix pour éviter cette hérésie de laisser ton objet modèle Plante directement modifier ton IHM NSMenuItem.
Merci.
Le simple fait d'avoir énoncé le problème m'a amené à réfléchir sur les singletons.
J'ai de fait constaté la nocivité que tu signales "surtout à ne pas utiliser quand il y a potentiellement du sens que ta classe puisse être instanciée".
Que sont les "objets métiers" ?
J'ai cherché sharedInstance : il y a toujours des blocs !
Est-il vraiment nécessaire d'utiliser des blocs ?
J'ai lu "https://developer.apple.com/library/mac/#featuredarticles/BuildingWithBlocks/_index.html".
Je ne suis vraiment pas convaincu : cela me semble un outil utile pour développeur système, non ?
Au contraire des boutons, fenêtres ou contrôleurs, qui n'ont rien de spécifique au métier.
En fait, le concept est simple. En gros, il existe une instance maintenue par la classe:
Maintenant, ceci n'est qu'un exemple, parce que cette implémentation n'est pas "thread safe". Tu verras donc dans les exemples qu'ils utilisent un dispatch_once pour éviter tout problème.
Les blocs sont devenus omni-présents dans Cocoa en deux ans. On les utilise dès que le code est asynchrone, ce qui est tous les jours plus courant (fin des animations, réponse d'un serveur web, etc.).
Moi ce que j'appelle les objets métier ce sont les qui font des traitements propres à ton métier / à ton application. Par exemple des classes qui implémentent un algo propre à ton appli (IA de ton jeu, ...), une classe qui communique avec ton WebService... bref bien souvent des classes en fait où ça a du sens qu'il n'y ait que des méthodes de classe (et on en fait un singleton plutôt que de justement juste faire des méthodes de classe parce qu'on a quand même besoin de @property en interne), les classes où ça te fournit un "service"...
Oui et c'est loin d'être le seul défaut qu'elle a, ce genre d'implémentation est trop simpliste et ne prend pas en compte pas mal de cas tordus il me semble. Et c'est effectivement cette implémentation qu'il faut utiliser, car elle règle normalement tous les petits problèmes subtils auxquels tu n'aurais pas pensé. Je veux dire par là qu'il a été approuvé maintenant que pour créer une sharedInstance c'était le pattern de code à utiliser, qui permettait d'être thread-safe, d'éviter d'écraser la sharedInstance ou de donner accès en écriture à cette dernière, faire du lazy-loading quand même... alors que si tu fais une implémentation à ta sauce, comme le code mis en contre-exemple par Céroce au dessus, tu risques fort de ne pas penser à ces cas tordus. Autant profiter du fait que cette implémentation est testée et approuvée et recommandée par Apple et Thread-Safe plutôt que réinventer la roue.
Je confirme. Perso je ne pourrais plus m'en passer, je me demande tous les jours comment on faisait sans avant. C'est le principe de Closure qu'on trouve dans plusieurs autres langages et qui manquait à ObjC à ses débuts, alors que maintenant ça fait partie intégrante du langage et ça change la vie !
Tu m'as mis le doute; mais ce que j'ai écris est correct.
Par contre, je suis d'accord avec toi: c'est aberrant d'en faire un singleton. Alors, va savoir pourquoi tant de gens le font dans leurs applis. (En fait, si, je sais pourquoi: par manque d'expérience et de réflexion).
Pour moi l'exemple de Céroce est valide.
En fait les objets métiers peuvent être constitués de données (les objets modèles cités par Céroce) et de processus (une classe utilitaire qui saurait causer avec un webservice métier).
Pour moi, c'est une hérésie d'employer le mot hérésie.
Le lien direct n'est pas une hérésie, c'est juste une modalité de liaison entre objets qui est rapide à coder et à exécuter mais qui a le gros désavantage de lier très fortement deux objets. Ce qui dans certains cas n'est pas un problème. Par exemple, entre un objet "mois" et un objet "jour", un lien direct est envisageable.
En revanche entre un objet IHM et un objet model (surtout dans le cas de ce post), le lien direct est effectivement moins adapté car on peut imaginer des tas de cas d'utilisation de l'objet model sans objet IHM. Concrêtement, cela veut dire que quand on va vouloir réutiliser l'objet model ailleurs, on va galérer avec des includes et des bouts de code qu'il va falloir retirer sans savoir s'ils sont importants ou non pour le fonctionnement de l'objet. On a du mal à imaginer la galère que cela peut être tant que l'on ne l'a pas fait sur un gros projet.
Après il faut relativiser en fonction du contexte (temps de développement disponible, probabilité de réutilisations).
En fait, il faudrait faire un tableau avec tous les types de liens possibles et indiquer les avantages et inconvénients de chacun en termes de lisibilité, réutilisabilité, vitesse d'exécution, vitesse de développement, etc.
Les types de liens disponibles étant :
lien direct par pointeur, observer, notification, delegate/protocol, block
Franchement, faire un @protocol pour faire de l'abstraction ça prend pas plus de temps que faire un lien direct.
Par contre, ne pas prendre 2s pour le faire peut coûter très cher après coup. Sur le moment ça te parait te gagner du temps (alors que franchement, ça te prend que 30s à créer un fichier pour écrire le @protocol et l'utiliser ensuite dans ta classe pour découpler le tout, et ça devrait être naturel), mais après coup tu le regrette bien vite quand tu reprends ton code.
Prendre le temps de réfléchir pour faire une architecture logicielle réutilisable est loin d'être un luxe. Ca peut te paraà®tre une perte de temps au début, mais ça t'en fera gagner beaucoup à la fin, en terme de debug, d'évolution, de respect des patterns (les patterns sont pas là pour faire jolis, ils existent parce qu'ils répondent à des problématiques courantes et qu'ils y répondent correctement, en fournissant des solutions éprouvées, testées, qui résolvent des soucis, et surtout le fait que tout le monde les connaisse et utilise la même base aide fortement à reprendre des projets existants quand on retombe dessus)
Et encore là je parle même pas de se poser et d'écrire une archi logicielle genre un UML ou quoi, je parle juste de respecter les patterns de base de la POO en général (et du MVC en particulier). Et là c'est plutôt clair, évidemment que tu peux faire des liens directs entre tes objets Modèle, mais entre le M et le V faut déjà mieux passer par un Controller, et il est fortement conseillé du coup de faire de la décorellation (observer, notif, protocol, block).
Ce qui me choque dans ce qui est évoqué ici, n'est pas tant qu'il y ait un lien direct, c'est que ce lien direct soit entre Plante et NSMenuItem, et donc entre un objet Métier et un objet Vue surtout. Ca, c'est une hérésie en POO en général, en MVC en particulier, si tu veux avoir un programme bien pensé et qui ne soit pas une plaie à débugguer même quand il va évoluer plus tard, et que tu veux avoir des éléments réutilisables et peu contraints. Et surtout ça prend 2s de plus à faire un @protocol plutôt qu'un lien direct, donc le temps de dev disponible n'est pas une excuse... au contraire même je dirais, car moins tu as de temps moins tu as de raison de réinventer la roue plutôt que d'utiliser des patterns tout faits qui vont éviter de te faire perdre du temps en garantie plus tard.
Sur le fond, tu as raison.
Sur la forme, c'est juste que je préfère que le gens apprennent en faisant des erreurs plutôt qu'en intégrant des interdits. Tant qu'un projet est bien encadré, quelques erreurs sont acceptables.
Quand tu dis que ça ne prends pas très longtemps, tu as raison pour ce qui est du temps de codage pur.
Mais il faut également prendre un temps de réflexion pour faire un protocol réutilisable ou "un peu ouvert".
Dans le cas précis de ce post, si c'est pour faire un protocole hyper spécifique, cela ne sert à rien.
Dans ce cas, voici ce que je ferais :
-Si le menu n'agit pas sur le modèle, j'enverrai une notification à partir de la plante et je la traiterai dans le contrôleur ou le menu.
-Si le menu agit sur les plantes, ce qui est probable, alors je mettrai du code de liaison dans le contrôleur, quitte à découper le contrôler en plusieurs fichiers et à réserver un fichier pour ranger le code d'interaction avec les plantes.
Dans ce cas ce n'est pas une bonne idée d'utiliser une notification puisque le contrôleur aura des liens directs vers le modèle, autant les utiliser en surveillant via les KVO.
Donc effectivement, c'est une très mauvaise idée de faire connaà®tre au modèle, l'existence d'une IHM.
Pour éviter cela, un bon moyen de réfléchir est de s'imaginer qu'on va développer plus tard un outil en ligne de commande travaillant sur le modèle ou, encore mieux, de faire des tests unitaires dans lesquels on s'interdit toutes dépendances avec autre chose que le modèle.
Le contrôleur aurait donc des liens directs vers les objets du modèle et les objets du modèle eux n'auraient aucune idée qu'il existe un contrôleur.
C'est un point important : dans l'idéal les objets du modèle n'ont pas à savoir comment ils sont affichés (fenêtre, texte) ou manipulés (touch, souris , clavier).
En pratique, on a parfois besoin d'afficher une valeur issue du modèle avec un formattage spécifique qui n'est connu que du modèle.
Ce qui n'est pas un cas que j'arrive à bien traiter par la méthode MVC.
Un cas concret est le portage d'un app iOS sur Mac. Un bon modèle sera portable sans modification.
En revanche, les controllers ne seront pas récupérables, si on y a placé du code de transformation du modèle qu'on voudrait récupérer alors c'est un peu le bazar pour dépatouiller les parties du contrôlleur qui sont liés à UIKit et celles qui sont de la transformation du modèle (par exemple, la transformation d'une distance en miles ou en km)
Un article assez intéressant sur le sujet http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/
On écrit nos tests unitaires d'abord, on code ensuite.
Pour le cas présent, pour le test unitaire il serait impossible d'avoir un lien entre le Modèle et l'UI, le TU ne testant que le fonctionnel. Donc on aurait tout de suite vu le souci de conception.
Le problème est en fait que le modèle MVC de Cocoa tel qu'il est proposé fait que dans les UIViewControlleurs, on accède directement à des objets de UIKit.
Si on voulait faire du vrai MVC et faire les choses bien, on devrait rajouter une couche d'abstraction. Car en réalité on a pas vraiment de couche "Vue" en Cocoa comme l'entend le MVC, on devrait en rajouter une pour chacun de nos écrans.
Par exemple si on a à un moment donné une tâche qui consiste à afficher les détails d'une plante à l'écran, plutôt que le Controller ait des propriétés sur des éléments de UIKit genre
et que pour remplir les champs avec les infos d'une plante il fasse
Il serait plus judicieux dans un premier temps de créer une classe PlantScreen qui n'expose que des paramètres de Foundation, et pas de UIKit, genre :
Et comme ça, le Controller peut toujours faire ses traitements intermédiaires, mais quand il s'agit d'afficher ensuite les infos à l'écran, plutôt que d'avoir besoin de connaà®tre la structure de l'UI, en particulier que c'est composé d'un UILabel et d'un UISwitch, il n'a besoin de savoir qu'il a juste à passer une NSString et un BOOL. Il n'a plus besoin d'une propriété par élément UIKit de l'interface, mais que d'une propriété qui retient le PlantScreen, et il passe à ce PlantScreen que des paramètres de Foundation, sans avoir de dépendance à UIKit de son côté.
Ainsi, le jour où on change la vue pour remplacer le UISWitch par un composant maison qui affiche un rond vert si c'est YES et un rond rouge si c'est NO, par exemple, alors on ne changera que l'implémentation de PlantScreen, sans toucher au Controlleur.
Cette solution garde quand même le problème qu'on ne peut pas brancher facilement un autre objet gérant l'affichage, et que le Controlleur est quand même lié à UIKit par le fait que PlantScreen est une sous-classe de UIView. La solution est alors de créer un protocole intermédiaire pour pouvoir mettre, comme objet qui gère l'affichage de la plante, non pas forcément un PlantScreen mais potentiellement autre chose :
On garde la même implémentation de PlantScreen.m, mais le Controller n'a plus une propriété "@property PlantScreen* plantScreen;" sur laquelle il appelle la méthode, mais une propriété "@property id<PlantDisplayer> display" à la place :
L'avantage est que si on veut mettre un autre écran qui sait afficher une plante mais qui est différent (par exemple on a un écran dédié aux botanistes qui affiche plein d'infos détaillées, un écran plus simple pour les néophytes, un écran avec un look encore différent...) on peut... et si on veut créer un outil en ligne de commande qui affiche la plante en ASCII dans le terminal, on peut aussi, puisqu'il n'y a aucune dépendance avec UIKit ! On pourrait donc imaginer une classe du genre :
Et là on aurait vraiment qqch de flexible. Ca ça serait du vrai MVC poussé jusqu'au bout.
Donc en conclusion, la solution idéale serait un @protocol pour faire une abstraction entre le Controller et la Vue + Un classe dédiée par écran de l'application, plutôt que de tout mettre dans le Controller.
Mais du coup un écran sur une appli iPhone par exemple nécessiterait :
- une sous-classe UIViewController pour la gestion (le C du MVC) de l'écran
- une sous-classe pour l'affichage (partie V du MVC) " ou plusieurs si on a plusieurs écrans ou qu'on veut gérer la sortie graphique et la sortie terminal, etc
- un @protocol pour abstraire la connexion entre le C et le V et pouvoir facilement remplacer la vue par une autre vue
Ce qui apporterait certes beaucoup de flexibilité mais serait quand même un peu lourd à force d'avoir beaucoup d'écrans...
En revanche si on doit gérer les actions venant de l'utilisateur dans cette classe on se retrouve à implémenter un mini-controller axé sur quelques contrôles. Ce qui peut-être une solution en soi. D'ailleurs depuis l'introduction des parent/child controller cette solution peut-être plus facilement implémentée sous iOS.
On aurait un controller parent réutilisable et des child controller jetables car très lié au système (je pense au controller pour les toolbars, les delegate de Gesture Recognizers).
Le problème c'est que sous iOS, les controllers (enfin les miens en tous cas) sont vraiment infâmes : ils gèrent non seulement la vue principale mais aussi les toolbars et éventuellement la barre de recherche, les data sources, les gesture recognizers, etc.
Une autre manière d'implémenter cette approche serait d'utiliser des category en tant que mini-controller.
Par exemple une categorie Plante+UISwitch, totalement jetable, mais qui aurait le mérite de décharger le controller qui se contenterait d'associer le UISwitch à la Plant.
Dans tous les cas l'approche que j'utilise pour réfléchir est la suivante : J'ai deux extrémité "pure" qui sont le modèle et la vue. Ces deux là doivent rester réutilisable.
Cocoa me fournit les vues, elle sont forcément réutilisables (dans iOS) et je fournis le modèle (réutilisable dans iOS et OSX).
Entre les deux il y aura forcément une zone dans laquelle le code ne sera pas réutilisable. La partie "sale".
Le jeu est de restreindre cette partie "sale" à la plus petite taille possible.
Dans iOS 3/4 c'était très difficile quand tout le code du milieu se retrouvait dans un seul contrôleur.
Du coup, on a dans le controller :
-du code hyper spécifique à UIKit (implementation de delegate et de datasources, création de toolbars, etc)
-et du code de transformation du modèle qui pourrait être potentiellement réutilisable comme le code qui va dessiner une plante sur une UIImage ou, plus difficilement comme le code qui va transformer des coordonnées Lat/Lon issu du modèle en un punaise sur une carte.
Le bon sens c'est de ne pas faire le dessin dans le contrôleur mais on ne peut pas non plus le faire dans le modèle car on va utiliser des API non réutilisable en tests unitaire ou en ligne de commande (CGContexteRef, etc) ; donc on utilise une catégorie.
PlantDisplayerIOS n'est pas réutilisable sur OSX, mais on va pouvoir facilement le dupliquer par analogie en utilisant les contrôles de l'API Mac AppKit. En fait on voit que le simple découpage du code en parties aillant le même facteur de réutilisabilité (ou le même degré de saleté) facilite la réutilisation et le portage. Autrement dit, si on ne peut pas faire un code directement réutilisable, il faut l'organiser de manière à ce qu'il soit réutilisable bêtement (par un stagiaire qui passe par là ).
PlantDetailController est surement sale car il mélange un peu tout, mais le fait de l'avoir allégé va peut-être permettre de le paramétrer avec des #ifdef pour OSX et iOS, sinon il faudra le refaire ou extraire dans des catégories les parties réutilisables.
Plant+Image est soit totalement réutilisable, soit facilement réutilisable (passage NSImage vers UIImage par exemple). On pourra s'en passer pour les tests unitaires du model ou au contraire le réutiliser pour générer des images en ligne de commande dans un outil de tests semi-automatique.
Intéressant, mais il y a environ dix acronymes dans cette article (STUPID, SOLID, DRY, KISS, etc).
Ca m'a donné une idée d'acronyme en tous cas : TATA (Trop d'Acronymes Tue l'Acronyme).
Ca s'utilise comme ça :
L'architecte est sympa mais il est complètement TATA, ça veut dire qu'il utilise trop d'acronymes et qu'on ne comprend rien à ce qu'il veut qu'on fasse.
Ca peut donner lieu à des quiproquo, comme tous ces acronymes qui sont trop généraux et réducteur.
Un exemple simple d'utilisation pour en comprendre l'intérêt dans le cas de la réponse d'un serveur (ici PgSQL), SVP
Par contre, voici un exemple avec NSURLConnection:
Dans cet exemple, la requête http est envoyée de façon asynchrone, c'est à dire qu'on envoie la requête, puis la main est rendue au programme. C'est seulement quand une réponse sera reçue du serveur que le bloc sera appelé.
Avant que n'existe cette méthode, qui prend un bloc en paramètre, il fallait:
- instancier une NSURLConnection et se mettre en délégué
- implémenter les méthodes déléguées nécessaires
- appeler la méthode -[start]
C'était beaucoup de code, éparpillé dans une classe.
Détail :
Je ne vois pas trop la différence entre un serveur web "nadot.net:80" et un serveur PostgreSQL "nadot.net:5432", sauf que dans le deuxième cas l'url a des paramètres compliqués... C'est pour cela que j'utilise le framework PGSQLkit plutôt que les classes Cocoa. Il marche d'ailleurs très bien.
Blocs :
Je crois que je commence à deviner.
Est-ce que je me trompe si je dis que cela consiste à ajouter en ligne le corps d'une fonction dont on a seulement le prototype ? Par analogie, comme si on avait un fichier .h, mais pas de fichier .m.
Oui, mais si on a besoin du résultat de la requête pour continuer ? Comment faire un "attendre jusqu'à ce que le résultat de la requête soit reçu" ?
J'apprend beaucoup de choses dans cette discussion. Merci.
Est-ce que je pourrais envoyer mon application à quelqu'un pour critiques sur la programmation ?
Seulement, les blocs possèdent une propriété particulière très intéressante: ils conservent leur contexte. Voici un exemple pour comprendre. Cet exemple utilise une méthode qui sert normalement à animer les vues, mais là , je m'en sers parce que le completionHandler est un bloc appelé à l'issu de l'animation:
Qu'affiche ce code ? La réponse:
En effet, le bloc de terminaison a capturé le contexte lors de sa création, en l'occurence, la valeur de la variable.
Ce n'est pas ainsi qu'il faut raisonner. Le fait que le programme se poursuive est positif: ça veut dire que le thread n'est pas bloqué, et donc que l'interface utilisateur continue à réagir aux actions de l'utilisateur. Maintenant, imaginons que la fenêtre principale contienne une image view. Afficher l'image une fois téléchargée est aussi simple que:
On n'attend donc pas que l'image soit chargée. Dans ce cas, ou pourrait imaginer afficher un indicateur tournant au dessus de l'imageView pour indiquer que l'image se charge.