Singleton ou NSUserDirectory ou autre ?

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.


 


Réponses

  • AliGatorAliGator Membre, Modérateur
    Que ce soit en Cocoa ou pas en Cocoa, le pattern singleton est vraiment à  réserver quand il y a du sens, et surtout à  ne pas utiliser quand il y a potentiellement du sens que ta classe puisse être instanciée plusieurs fois (question à  se poser à  plus grande échelle que ton application au cas où tu pourrais réutiliser cette classe dans d'autres projets dans le futur).

    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.
  • mybofymybofy Membre
    août 2013 modifié #3

    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 ?


  • CéroceCéroce Membre, Modérateur

    Que sont les "objets métiers" ?

    Comme le nom l'indique, ce sont les objets qui sont en rapports avec le but même de l'application. Par exemple, si tu as une application Emploi du temps, ça sera des objets Mois, Jours, Rendez-vous, etc.
    Au contraire des boutons, fenêtres ou contrôleurs, qui n'ont rien de spécifique au métier.

    J'ai cherché sharedInstance il y a toujours des blocs !

    En fait, le concept est simple. En gros, il existe une instance maintenue par la classe:


    static PrintDialogue *_sharedPrintDialogue;

    @implementation

    + (PrintDialogue *) sharedPrintDialogue
    {
    if(_sharedPrintDialogue == nil)
    _sharedPrintDialogue = [[PrintDialogue alloc] init];

    return _sharedPrintDialogue;
    }
    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.

    Est-il vraiment nécessaire d'utiliser des blocs ?
    Je ne suis vraiment pas convaincu : cela me semble un outil utile pour développeur système, non ?

    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.).
  • AliGatorAliGator Membre, Modérateur

    Comme le nom l'indique, ce sont les objets qui sont en rapports avec le but même de l'application. Par exemple, si tu as une application Emploi du temps, ça sera des objets Mois, Jours, Rendez-vous, etc.

    Heu non tu confonds avec le objets Modèle là  Céroce ;) Et eux j'ai jamais vu aucun cas d'usage où ça avait du sens d'en faire des Singletons, ça me semble même plutôt une aberration.

    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"...

    En fait, le concept est simple. En gros, il existe une instance maintenue par la classe:
     

    static PrintDialogue *_sharedPrintDialogue;

    @implementation

    + (PrintDialogue *) sharedPrintDialogue
    {
    if(_sharedPrintDialogue == nil)
    _sharedPrintDialogue = [[PrintDialogue alloc] init];

    return _sharedPrintDialogue;
    }
    Maintenant, ceci n'est qu'un exemple, parce que cette implémentation n'est pas "thread safe".

    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.

    Tu verras donc dans les exemples qu'ils utilisent un dispatch_once pour éviter tout problème.

    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.

    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.).

    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 !
  • CéroceCéroce Membre, Modérateur

    Heu non tu confonds avec le objets Modèle là  Céroce ;) Et eux j'ai jamais vu aucun cas d'usage où ça avait du sens d'en faire des Singletons, ça me semble même plutôt une aberration.



    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).


     



    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.



     


    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

  • AliGatorAliGator Membre, Modérateur
    Même si ce mot te choque, je redis que faire un lien direct entre un objet métier et un objet vue est une hérésie. On ne fait pas dépendre le modèle de la vue.

    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.
  • FKDEVFKDEV Membre
    août 2013 modifié #9

    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.



    @protocol MauvaisProtocol
    -(void)updateMenu:(BOOL)state;
    @end

    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/


  • AliGatorAliGator Membre, Modérateur
    août 2013 modifié #11

    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.

    Oui, c'est pour ça que j'aime et préconise le TDD.
    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.
     

    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)

    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
    @property(strong) IBOutlet UILabel* nameLabel; // Pour le nom de la plante
    @property(strong) IBOutlet UISwitch* eatableSwitch; // pour savoir si la plante est comestible
    et que pour remplir les champs avec les infos d'une plante il fasse
    self.nameLabel.text = plant.name;
    self.eatableSwitch.on = plant.isEatable;
    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 :
    @interface PlantScreen : UIView
    - (void)displayPlantWithName:(NSString*)name eatable:(BOOL)eatable;
    @end
    @implementation PlantScreen
    {
    IBOutlet UILabel* _nameLabel;
    IBOutlet UISwitch* _eatableSwitch;
    }
    - (void)displayPlantWithName:(NSString*)name eatable:(BOOL)eatable
    {
    _nameLabel.text = name;
    _eatableSwitch.on = eatable;
    }
    @end
    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 :
    // PlantDisplayer.h
    @protocol PlantDisplayer : NSObject
    - (void)displayPlantWithName:(NSString*)name eatable:(BOOL)eatable;
    @end
    // PlantScreen.h
    @interface PlantScreen : UIView <PlantDisplayer>
    @end
    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 :
    [self.display displayPlantWithName:plant.name eatable:plant.isEatable];
    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 :
    @interface PlantLogger <PlantDisplayer>
    @end

    @implementation PlantLogger
    - (void)displayPlantWithName:(NSString*)name eatable:(BOOL)eatable {
    NSLog(@Plant %@ %@", name, eatable ? @is eatable : "is not eatable");
    }
    @end
    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...
  • Cette approche est intéressante pour cacher les différences entre UIKit et AppKit.
    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.
     
     
     

    UIImageView 
    UILabel      -- PlantDisplayerIOS -- PlantDetailController -- Plant+Image -- Plant 
    UISwitch

     
     
     
    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.
  • FKDEVFKDEV Membre
    août 2013 modifié #13

    Un article assez intéressant sur le sujet http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/


    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.


  •  


    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.).

     




     


    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

  • CéroceCéroce Membre, Modérateur

    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

    Il n'y a pas trop de rapport entre PostgreSQL (qui est un SGBD) et un serveur, sauf qu'effectivement, la BdD sera souvent hébergée sur un serveur externe.

    Par contre, voici un exemple avec NSURLConnection:
    NSURL *url = [NSURL URLWithString:@http://www.monsite.com/image.png];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *error)){
    if(data)
    self.image = [NSImage imageWithData:data];
    else
    NSLog(@Error: %@", error);
    }];
    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.
  • mybofymybofy Membre
    août 2013 modifié #16

    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.


     



     


     


    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é.

     


    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 ?


  • CéroceCéroce Membre, Modérateur

    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.

    Hum, pas tout à  fait. Certains langages de programmation possèdent une notion de "fonction anonyme"; il s'agit d'une fonction qui est passée en paramètre _en ligne_ comme dans l'exemple ci-dessus.
    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:


    int variable = 3;
    NSLog(@variable = %i, variable);
    [UIView animateWithDuration:1.0 animations:^{
    // Ne rien faire
    } completion:^(BOOL finished) {
    NSLog(@variable = %i, variable);
    }];

    variable = 7;
    NSLog(@variable = %i, variable);
    Qu'affiche ce code ? La réponse:

    variable = 3
    variable = 7
    variable = 3
    En effet, le bloc de terminaison a capturé le contexte lors de sa création, en l'occurence, la valeur de la variable.
     

    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" ?

    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:


    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *error)){
    if(data)
    {
    NSImage *image = [NSImage imageWithData:data];
    self.imageView.image = self.image;
    }
    else
    NSLog(@Error: %@", error);
    }];
    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.
Connectez-vous ou Inscrivez-vous pour répondre.