Design patterns et best practices

NSMaximeNSMaxime Membre
mars 2015 modifié dans API UIKit #1

Bonjour à  tous,


 


Je souhaiterais avoir vos conseils, retours d'expériences, critiques sur les différents design patterns les plus utilisés en Swift (ou Objective-C).


  • Singleton
  • MVC
  • Decorator
  • etc...

Et également quelles sont vos best practices concernant le développement?


  • Développer en priorité le design?
  • Commentaires ou docs?
  • Règles de nommage
  • etc...

Merci par avance!


 


 


Mots clés:
«1

Réponses

  • Vaste question. Une réponse : YAGNI c'est a dire you ain't gonna neee it. Pas besoin de mettre en place l'interface la plus generale possible si tu n'en as pas besoin. On dit aussi KISS keep it stupid simple.
  • AliGatorAliGator Membre, Modérateur
    mars 2015 modifié #3
    - MVC always


    - Singleton : seulement pour les Services (et encore ça se discute)


    - Le reste : ça dépend énormément du contexte, comme pour tout DP. Clairement YAGNI car vouloir mettre des DP juste pour le principe sans justification ça n'est pas utile. Par contre il faut quand même connaà®tre les DP usuelles pour savoir quand elles peuvent être utiles selon le contexte et quand elles peuvent te solutionner efficacement un problème classique que t'y rencontres " car après tout c'est leur rôle premier. Par exemple ça serait idiot de tenter de ré-inventer la logique autour d'un concept quand un DP existe précisément pour ce genre de cas.


    Pour tout ça, il faut connaà®tre les DP usuelles leur fonctionnement et leur rôle pour savoir quand les appliquer... et quand s'en passer. J'en connais qui après avoir lu un bouquin sur les DP avaient tendance à  les utiliser au maximum partout même là  où ça complexifiait le code pour rien. C'est inutile et contre-productif. J'en connais d'autres qui n'en respectent aucune et c'est pas mieux vu que ça finit en plat de spaghetti.

    Il n'y a pas de réponse absolue, il faut savoir les appliquer à  bon escient, et ça par définition ça défaut énormément du contexte, donc on peut pas trop te répondre avec une réponse tranchée. Le reste vient avec les années d'expérience et la sensibilité de chacun à  avoir une vision globale système (à  avoir l'âme d'un architecte quoi).


    En pratique celles que j'ai eu le plus l'occasion d'utiliser sont MVC, Abstract Factory, Builder, Dependency Injection, Control Inversion & Delegation. Façade et Adapter aussi (merci les projets legacy) de temps en temps. Object Pool aussi mais bien plus rare. Très rarement Visitor (en même temps je fais peu de parcours d'arbre)
  • AliGatorAliGator Membre, Modérateur
    mars 2015 modifié #4
    Concernant sinon les étapes type de développement, **dans l'ordre** (j'en oublie certainement je liste à  chaud) :

    - Créer les wireframes sur papier (ou outil informatique dédié) et demander/rédiger les SFG d'abord, toujours. ça n'a pas de sens de partir dans le code si on sait pas ce qu'on est sensé faire.

    - SFD et Contrats d'interface de WS sont demandés ensuite. Pendant ce temps, phase d'architecture, Data Model, etc.

    - Fixtures et Tests Unitaires de WS d'abord

    - Implementation du Data Model, des WS et du parsing ensuite

    - XIB/Storyboard enfin

    - Recette interne

    - Itérations régulières (Sprint type Agile)


    Concernant les Best Practices :

    - TU du code

    - Documentation du code

    - Respect des conventions de nommage Apple et des conventions définies pour l'entreprise (pour que tous les projets utilisent les memes)

    - Utilisation d'une chaà®ne de compilation rodée de A à  Z (CocoaPods, GIT, serveur de CI, Livraison automatisée,...)

    - Suivi de process établis (tels GIT Flow pour pouvoir développer des features en parallèle quand on est plusieurs sur le projet, etc)

    - Séparation des composants en Pods (même locaux au projet, type Development Pods) quand cela a du sens, ce qui permet d'inciter à  une architecture en module

    - Code Review réguliers par un archi, même si ça veut dire mettre en pause les devs une journée le temps de faire un refacto pour démêler les noeuds " dans le cas où la vie du projet à  fait que c'est parti en vrille " plutôt que de remettre à  plus tard et jamais faire et retrouver l'horreur en VABF ou en TMA.
  • Salut Ali,


     


    Toujours intéressantes tes réponses mais pas toujours claires dans le détail. Tu pourrais décoder un peu s'il te plait (ou peut-être quelqu'un qui parle les mêmes abréviation) :


    • DP
    • YAGNI
    • SFG
    • SFD
    • WS
    • TU
    • VABF
    • TMA
  • AliGatorAliGator Membre, Modérateur
    DP = Design Patterns
    YAGNI

    SFG/SFD = Specifications Fonctionnelles Générales / Détaillées

    WS = WebServices

    TU = Tests Unitaires
    VABF / TMA
  • Et SGZ ? C'est une extension utilisé à  une époque chez UBI Soft. Quelqu'un sait ce que cela veut dire ?

  • En fait c'est impossible de connaà®tre, à  moins d'avoir fait partie du petit groupe des premiers développeurs d'UBI Soft. C'est un private-joke signifiant Style Genre Zarma, pour se moquer des acronymes pompeux. Plusieurs jeux UBI développés dans les années 1990 utilisent des fichiers .SZG !

  • Merci pour ces réponses.


    Personnellement j'utilise tout le temps MVC et très souvent la factory ou les singletons. Je vais donc regarder dans quels cas on peut utiliser chacun de ces DP.


     


    Concernant les best practices:


    @Ali: Pourrais-tu m'en dire un peu plus sur les outils utilisés pour les TU et également l'intégration continue (free and paid)?


  • Et pourquoi vous n'aimez pas les singletons ?


  • CéroceCéroce Membre, Modérateur

    Et pourquoi vous n'aimez pas les singletons ?

    Ce sont des globales.
    Ce sont des dépendances cachées.
    ça rend difficile l'écriture de tests unitaires.
  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #13


    Ce sont des globales.

    Ce sont des dépendances cachées.

    ça rend difficile l'écriture de tests unitaires.




     


    Et, pour utiliser Core Data ? Par défaut, le managedObjectContext est fourni par une méthode d'instance sur la seule instance de l'appDelegate, qui soit un singleton. Pourquoi pas séparer la pile Core Data en sa propre classe singleton afin que l'on puisse l'accéder sans avoir "trouver" l'appDelegate chaque fois ?


     


    Un des buts de POO est d'éviter les dépendances. Si l'on déclare un protocol ou contrat pour un comportement, on n'a pas le droit de connaà®tre ce qui se passe "derrière le rideau" ; du coup, tout ce qui se passe est implicitement caché, même les dépendances.


     


    Les test unitaires peuvent être fait sur un singleton. Il ne faut que rien faire dans la méthode d'initialisation.


     


    Non, de mon avis, si l'on a besoin d'une seule instance d'une classe, les singletons présentent le meilleur choix.


  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #14

    Côté DP Visitor, Je l'utilise souvent si j'ai une collection d'objets qu'il faille dessiner et stocker. On peut, alors, séparer les comportements de dessin et stockage des comportements "fonctionnels" des classes.


  • CéroceCéroce Membre, Modérateur
    mars 2015 modifié #15

    Et, pour utiliser Core Data ? Par défaut, le managedObjectContext est fourni par une méthode d'instance sur la seule instance de l'appDelegate, qui soit un singleton.
    Pourquoi pas séparer la pile Core Data en sa propre classe singleton afin que l'on puisse l'accéder sans avoir "trouver" l'appDelegate chaque fois ?

    Oui, le code d'Apple crée la pile Core Data dans l'AppDelegate, et oui, c'est complètement idiot, puisque ça pousse à  écrire:

    [(AppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext].
    Donc, oui, je crée une classe CoreDataStack.

    Maintenant, pourquoi en faire un singleton, alors qu'on peut l'instancier et la passer ? En pratique, passer le Managed Object Context suffit.
    On n'a pas de certitude qu'il n'y aura qu'une seule base Core Data dans l'appli, donc le singleton ne doit pas être utilisé.
     

    Un des buts de POO est d'éviter les dépendances. Si l'on déclare un protocol ou contrat pour un comportement, on n'a pas le droit de connaà®tre ce qui se passe "derrière le rideau" ; du coup, tout ce qui se passe est implicitement caché, même les dépendances.

    Non, les dépendances restent explicites. En utilisant un protocole, on ne connait pas le type précis de cette dépendance, mais cette dépendance apparait explicitement dans le .h, sous la forme d'une propriété.
     

    Les test unitaires peuvent être fait sur un singleton. Il ne faut que rien faire dans la méthode d'initialisation.

    Les dépendances cachées peuvent modifier totalement le comportement d'un objet. Un exemple: NSUserDefaults. Si les préférences sont différentes, alors l'objet va fonctionner différemment s'il utilise ces préférences. Ainsi, le test unitaire peut passer à  un instant, et ne plus passer plus tard, alors que le code testé n'a pas évolué.

    J'ai déjà  eu des problèmes avec des formatages d'heure, par exemple. Le test passe en janvier, mais plus en avril, parce qu'il y a eu un passage à  l'heure d'été entre temps.
     

    Non, de mon avis, si l'on a besoin d'une seule instance d'une classe, les singletons présentent le meilleur choix.

    Je ne dis pas que les singletons ne sont pas pratiques. Ils le sont, et c'est bien pour ça qu'ils sont employés à  toutes les sauces.
    Mais les exemples de resource unique sont vraiment rares, et les singletons ont un coût de maintenance important.
  • AliGatorAliGator Membre, Modérateur

    @Ali: Pourrais-tu m'en dire un peu plus sur les outils utilisés pour les TU et également l'intégration continue (free and paid)?

    J'utilise ce qui est intégré à  Xcode pour les TU, à  savoir XCTest.
    Pour l'intégration continue ça dépend :
    • Jenkins est la solution la plus connue. Ceci dit ça a été pensé à  l'origine pour des projets en Java, ça marche pour des projets d'autre type mais c'est pas non plus tip top. Ceci dit c'est le plus répandu, et c'est ce qu'on utilise au boulot car on l'utilise pour compiler tous nos projets que ce soit iOS, Android, Web, etc
    • Pour les projets persos OpenSource ainsi que pour CocoaPods, j'utilise Travis-CI, bien plus adapté à  de l'intégration continue iOS/OSX
  • Merci, ça confirme les outils que j'utilise actuellement.


  • AliGatorAliGator Membre, Modérateur

    Et, pour utiliser Core Data ? Par défaut, le managedObjectContext est fourni par une méthode d'instance sur la seule instance de l'appDelegate, qui soit un singleton. Pourquoi pas séparer la pile Core Data en sa propre classe singleton afin que l'on puisse l'accéder sans avoir "trouver" l'appDelegate chaque fois ?
     
    Un des buts de POO est d'éviter les dépendances. Si l'on déclare un protocol ou contrat pour un comportement, on n'a pas le droit de connaà®tre ce qui se passe "derrière le rideau" ; du coup, tout ce qui se passe est implicitement caché, même les dépendances.

    Pour cela, il y a le design pattern de l'Injection de Dépendance (Dependency Injection).

    Donc au contraire, justement si tu utilises la DI, tu exposes, au travers d'un protocol, un contrat d'interface, la dépendance n'est pas cachée, contrairement justement à  si tu ton implémentation fais appel à  un singleton " où dans le .h tu ne vas pas voir cette dépendance au singleton au sein de l'implémentation, alors qu'avec la DI tu vas la voir. Et le fait que tu ne saches pas comment ce protocole est implémentée au moment où tu l'utilises dans ta classe qui dépend de ce protocole c'est totalement dans la philosophie de la POO et les principes d'encapsulation qu'elle prone.
    Alors qu'à  l'inverse si tu utilises un Singleton et appelle ce singleton dans ta classe A, cette dépendance de ta classe A avec le singleton est totalement masquée dans l'API, et dépend de son implémentation, et tu ne peux pas changer cette implémentation. Ce n'est plus un contrat d'intention, mais une dépendance forte, donc si l'un casse, l'autre casse. C'est justement ce que permet d'éviter l'injection de dépendance.
  • FKDEVFKDEV Membre
    mars 2015 modifié #19

    Dites moi si je me trompe mais une partie de ce jargon est dispensable car :


     


    -Inversion de contrôle = encapsulation (chaque objet/framework est responsable de ses données et des traitements sur ses données)


    -Dependency Injection = Utiliser des interfaces plutôt que des pointeurs vers des objets (ou protocol en ObjC), et par conséquent, ne pas créer un objet à  partir d'un autre objet, mais plutôt les faire se rencontrer par l'intémerdiaire d'un tiers.


     


    Le principe YAGNI devrait conduire à  utiliser moins de jargon.


  • FKDEVFKDEV Membre
    mars 2015 modifié #20
    Le problème du singleton est multiple:
     
    1/ C'est souvent une référence directe à  un objet plutôt qu'à  un protocol, par exemple : NSFileManager au lieu de IFileManager.
    2/ Comme signalé par Ali, c'est souvent une référence cachée car obtenue de manière directe au milieu du code via une méthode globale ([NSFileManager defaultManager]).
     
     
     
     

    Les dépendances cachées peuvent modifier totalement le comportement d'un objet. Un exemple: NSUserDefaults. Si les préférences sont différentes, alors l'objet va fonctionner différemment s'il utilise ces préférences. Ainsi, le test unitaire peut passer à  un instant, et ne plus passer plus tard, alors que le code testé n'a pas évolué.

     
    Le problème citée par Céroce n'est pas un problème de singleton à  proprement parler (car pas causé par les caractéristiques 1 et 2 citées ci-dessus) mais plus un problème d'architecture des test unitaires où la base de données sur laquelle NSUserDefault travaille n'était pas maitrisée dans les tests unitaires.
     
    La solution ici était donc de modifier la base de donnée sur laquelle NSUserDefault allait travailler. Si ce n'était pas possible, alors on pouvait envisager d'encapsuler NSUserDefault dans un autre objet qui aurait implémenter une interface IUserPreferences.
    Dans les tests unitaires on aurait pu remplacer cet objet par un autre objet qui aurait renvoyé des préférences connues.
     
    Cette interface IUserPreferences aurait très bien pu être récupérée par un accès global type singleton. [[AppDelegate sharedInstance] GetUserPreference].
    Ce n'est donc pas un problème avec le pattern singleton, ni un problème d'interface.
     
     
    DI est vraiment un pattern pas clair dans la mesure où il tente de régler deux problèmes distincts d'un seul coup (ou alors je n'ai pas compris) :
    -le problème de l'interface (ai-je affaire à  un objet "complet" ou à  une interface vers un objet)
    -le problème de l'obtention de l'interface ou du pointeur : par accès global, par paramétrage ou par un tiers.
     
     
    Interdire le singleton pour le remplacer par un pattern confus, c'est juste se priver d'un outil tout en ne comprenant pas vraiment pourquoi on s'en prive.
     
    Je préfère utiliser des singletons plutôt que d'avoir des méthodes de ce style pour tous mes objets:

    -(instancetype) initWithFileManager(IFileManager)filemanager andUserPreferences:(IUSerPreferences)prefmanager andWSManager:(IWSManager)wsManager


  • Le principe YAGNI devrait conduire à  utiliser moins de jargon.




    C'est clair qu'en face d'un dieu du feu hindou on évite de parler pour ne rien dire ..

  • FKDEVFKDEV Membre
    mars 2015 modifié #22

    Bonjour à  tous,
     
    Je souhaiterais avoir vos conseils, retours d'expériences, critiques sur les différents design patterns les plus utilisés en Swift (ou Objective-C).

    • Singleton
    • MVC
    • Decorator
    • etc...
     
    Les patterns il faut les essayer et les connaà®tre pour augmenter sa trousse à  outil et pouvoir communiquer avec les autres dev.
    Mais il est plus important de bien comprendre ce que l'on tente de faire avec ces patterns.
     
    En cuisine la question serait : faut-il battre les oeufs avec une fourchette ou avec un fouet ?
    La réponse : ça dépend des oeufs, du bras, de la taille du récipient et de la préférence du cuisinier.
    L'important étant la préférence du cuisinier, c'est-à -dire l'outil qui va lui donner la meilleure sensation de la texture de la matière première.
     
    Dans le dévelopement, les matières premières ce sont les flots de traitements, de données et (plus compliqué à  se représenter) la projection de ces flots vers l'utilisateur via le GUI.L'important c'est d'avoir une bonne sensation de ces flots tout en n'oubliant pas qu'on doit servir un utilisateur au final.
     
    Donc mon conseil : essayer les pattern mais surtout tenter de comprendre les problèmes qu'on a essayé de résoudre en les inventant (mais c'est difficile tant qu'on n'a pas expérimenté ces problèmes).
  • CéroceCéroce Membre, Modérateur

    Les dépendances cachées peuvent modifier totalement le comportement d'un objet. Un exemple: NSUserDefaults. Si les préférences sont différentes, alors l'objet va fonctionner différemment s'il utilise ces préférences. Ainsi, le test unitaire peut passer à  un instant, et ne plus passer plus tard, alors que le code testé n'a pas évolué.


    Le problème citée par Céroce n'est pas un problème de singleton à  proprement parler (car pas causé par les caractéristiques 1 et 2 citées ci-dessus) mais plus un problème d'architecture des test unitaires où la base de données sur laquelle NSUserDefault travaille n'était pas maitrisée dans les tests unitaires.


    C'est vrai que maà®triser la configuration de NSUserDefault, c'est super simple. Que celui qui configure toutes les valeurs de NSUserDefault et les fait varier pour chaque test me jette la première pierre.
  • AliGatorAliGator Membre, Modérateur
    Pour info si ça peut servir à  d'autres, concernant NSUserDefaults, dans ma classe mère dont j'hérite pour toutes mes Test Suites (plutôt que d'hériter de XCTestCase), j'ai :
    - (void)setup {
    [super setup];
    [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:@xctest];
    }
  • colas_colas_ Membre
    mars 2015 modifié #25

    Je n'ai pas beaucoup d'expérience et je travaille plutôt seul, donc mon point de vue est limité mais je trouve que les singletons c'est très pratique. ça permet depuis une classe de récupérer des infos facilement ou de lancer des actions.


     


    Voici quelques uns de mes singletons :


    - MyCBDImageProvider : les images


    - MyCBDGlobalSettings


    - MyCBDCommandsCenter : le centre de commandes qui peut lancer des actions, etc.


    - MyCBDDataProvider (qui peut être divisé en deux : MyCBDLocalDataProvider et MyCBDDistantDataProvider) : accès aux données


    - MyCBDGlobalInfo : UIFont, UIColor


     


    Si les singletons ne sont utilisés que depuis des contrôleurs qui ne sont pas appelés à  être réutilisés (c'est souvent le cas), les singletons ne posent pas de problèmes. Au contraire j'aurais tendance à  penser qu'ils facilitent le refactoring.


     


    ###############


     


    Autre point à  aborder (en objective-C) : le préfixage des classes. J'ai un peu loosé je pense. Je préfixe mes classes par MyCBD (je réserve CBD pour les frameworks que j'open-source), en voulant bien faire. Certains des membres de ce forum dirait qu'il ne faut pas préfixer...


  • Petite question subsidiaire :


     


    Dans le modèle MVC est-il envisageable, souhaitable, ou dangereux d'avoir deux Contrôleurs distincts qui communiquent avec le même Modele ou les 3 éléments doivent-ils toujours fonctionner ensemble ? 


     


    Si on peut le faire, comment faire communiquer les deux contrôleurs entre eux pour qu'ils se mettent d'accord sur l'instance de la classe modèle?


     


    Je suis pas sûr d'être bien clair, peut-être, comme dirait Ali : "S'il n'y a pas de solution, c'est qu'il n'y a pas de problème !"


  • Joanna CarterJoanna Carter Membre, Modérateur
    Tu peux "observer" le modèle, en utilisant le KVO, dans les deux contrôleurs.


  • Je suis pas sûr d'être bien clair, peut-être, comme dirait Ali : "S'il n'y a pas de solution, c'est qu'il n'y a pas de problème !"




    Tu vas prendre chaud en confondant Ali avec un Shadok. C'est pas exactement la même dentition.

  • Bonjour,


    mille excuses pour cette intrusion.


     


    J'ai lu ce post avec passion, et j'avoue que je ne comprend pas grand chose.


     


    Après avoir cherché sur le web ces deux définitions - Refactoring et Singleton, j'ai encore des doutes.


     


    Refactoring, c'est la phase où on renomme, et je ne sais pas trop quoi d'autre ?


    A quoi ça sert, si c'est bien nommé ?


     


    Un singleton, c'est un objet qui est l'unique instance d'une classe ?


    Si c'est ça, on l'utilise sans arrêt, non ?


    Par ex :



    var myLineView: LineView!
    var myRoundDView: RoundView!
    var myRoundGView: RoundView!

    Merci d'avance

  • Bonjour,


     


    Le refactoring consiste bien dans ce que tu comprends à  renommer par exemple des variables, des classes, etc...


     


    Attention! Il ne faut pas confondre déclaration d'un objet et instanciation! Dans l'exemple que tu prends ci-dessous, tu déclares simplement tes variables de type LineView ou RoundView, tu ne leur affectes pas de valeur.


     





    var myLineView: LineView!
    var myRoundDView: RoundView!
    var myRoundGView: RoundView!



     


    var myLineView: LineView (déclaration)


    myLineView = LineView() (instanciation)


    var myLineView: LineView = LineView() (déclaration et instanciation)


     


    En écrivant LineView je déclare une nouvelle instance d'un objet de type LineView.

  • busterTheobusterTheo Membre
    mars 2015 modifié #31

    Ouais, tu as grave raison - Merci.


     


    Et donc, le singleton - Instance unique ?


Connectez-vous ou Inscrivez-vous pour répondre.