IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Design Patterns Discussion :

Quel(s) Design Pattern ?


Sujet :

Design Patterns

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Août 2008
    Messages
    62
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 62
    Points : 59
    Points
    59
    Par défaut Quel(s) Design Pattern ?
    Bonjour,

    A titre d’exercice, afin de me familiariser d'avantage avec les designs patterns, je souhaiterais gérer une liste d’items via, sans doute, une interface permettant diverses implémentations pour le stockage de cette liste.

    Chaque item ayant une valeur et étant identifié par une "clef" composée (section, parametre)

    Les actions possibles seraient 1) Lecture 2) Ecrire 3) Suppression d’un item ou d’une section toute entière

    Par exemple :

    • Get( "section1", "parametre1b") -> valeur2
    • Set( "sectionX", "parametreY", "valeurZ") qui modifie la valeur si l’item existe, et sinon crée l’item (ainsi que la section si besoin)
    • Delete( "sectionX") qui supprime entièrement la sectionX, ou Delete( "sectionX", "parametreY") qui supprime le parametreY de la sectionX


    Get() Set() et Delete() seront vraisemblablement des méthodes d’un objet

    Nommons cet objet "configurationManager"

    Contraintes :
    • Il ne peut exister qu’un seul configurationManager dans tout le code (instance unique)
    • Il doit être possible de modifier la méthode de stockage des items sans rien changer au code "utilisateur", sinon sans doute à un unique endroit où l’on indiquera que le stockage se fait dans une base de données, ou un fichier XML, ou un fichier "INI" typique des anciens fichiers de configuration de Windows, ou peut-être même via un WebService, etc… Bref, on n'aura pas forcément prévu dès le départ tous les modes de stockage possibles, et il devra être possible d'en ajouter d'autres aisément en impactant au minimum sur le code utilisateur


    Afin de s’assurer de l’unicité de l’objet permettant les Get(), Set() et Delete() sur les items, j’ai bien entendu songé au Design Pattern Singleton. Avez-vous une objection à cela ?

    Il me semble acquis que cet objet unique serait obtenu par une méthode retournant "une interface" nommée par exemple IConfigurationManager, soit en C# quelque chose comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    interface IConfigurationManager
    {
    	public string Get( string section, string parametre)
    	public void Set( string section, string parametre, string valeur )
    	public void Delete(string section)
    	public void Delete(string section, string parametre)
    }
    Pour la suite, par contre, cela se corse…

    Quel(s) design(s) pattern(s) utiliser ?

    J’ai envisagé Factory Method, couplé à Strategy ? Au niveau de la mise en oeuvre, cela ne me semble pas si évident... Qu’est ce que cela donnerait au niveau du code ?

    Il me semble qu’il faudra créer des classes implémentant l’interface IConfigurationManager, par exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class ConfigurationManagerXML: IConfigurationManager()
    {
    …
    }
    class ConfigurationManagerDB : IConfigurationManager()
    {
    …
    }
    class ConfigurationManagerINI : IConfigurationManager()
    {
    …
    }
    Puis au niveau d’une factory avoir quelque chose comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class ConfigurationManagerFactory
    {
    	public static IConfigurationManager GetConfigurationManager(…)
    }
    Mais quels parametres pour GetConfigurationManager() ?

    Comment indiquer proprement la "stratégie" = le mode de "stockage" des items ?

    D’avance merci pour vos remarques !

  2. #2
    Membre émérite
    Inscrit en
    Janvier 2011
    Messages
    805
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Janvier 2011
    Messages : 805
    Points : 2 918
    Points
    2 918
    Par défaut
    A moins d'avoir besoin de créer toute une série de ConfigurationManager qui gardent un historique de leur état et de vouloir switcher entre eux à l'exécution, j'enlèverais la partie IConfigurationManager + Factory pour ne conserver qu'une classe ConfigurationManager.

    L'intérêt du pattern Stratégie est justement que la classe utilisatrice reste la même quelle que soit la stratégie qu'elle emploie, tout en ayant la possibilité de changer de stratégie au runtime, et de coder des nouvelles stratégies à mesure des besoins. Au fond, il s'agit de découpler la classe de la stratégie qu'elle emploie.

    Ici je ne vois pas trop l'intérêt de créer un niveau supplémentaire d'abstraction avec IConfigurationManager étant donné que "Il ne peut exister qu’un seul configurationManager dans tout le code (instance unique)".

    Autrement dit : Il n'y a pas d'intérêt à ce qu'une classe tierce puisse manipuler un ConfigurationManager de manière abstraite puisque c'est déjà la Stratégie contenue dans ConfigurationManager qui se charge d'abstraire (=cacher les détails de) la persistance de la configuration.

    Ca pourrait donner un truc du genre :

    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class ConfigurationManager
    {
       private static ConfigurationManager instance;
       public IPersistenceStrategy PersistenceStrategy { get; set; } // injection par setter
     
       private ConfigurationManager(IPersistenceStrategy persistenceStrategy) // injection par constructeur 
       {
         PersistenceStrategy = persistenceStrategy;
       }
     
       public static ConfigurationManager Instance // Attention singleton non thread-safe
       {
          get 
          {
             if (instance == null)
             {
                instance = new ConfigurationManager();
             }
             return instance;
          }
       }
     
    }

    Le problème d'un exemple purement théorique, c'est qu'on a vite fait de vouloir créer une solution en plaqué or en utilisant tous les patterns de la terre, chose qui ne serait pas forcément judicieuse dans une situation réelle (YAGNI est souvent une bonne solution à considérer)

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Août 2008
    Messages
    62
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 62
    Points : 59
    Points
    59
    Par défaut
    Merci pour l’acronyme YAGNI que je ne connaissais pas ! Il se retient moins aisément que DRY ou KISS mais il me plait quand même ;-)

    Sur le fond de ta réponse, une précision : pour toute la durée d’exécution du programme « utilisateur », la « source des données » doit rester la même : lors de la création de l’unique instance de type ConfigurationManager (ou dérivé, ou IConfigurationManager) on indique le mode de persistance des données une fois pour toute.

    L’intérêt de n’avoir pas plus d’une instance, c’est d’éviter qu’à tel ou tel endroit du code on travaille tantôt avec tel mode de persistance des données, tantôt avec tel autre, puis avec telle ou telle « source de données », etc… ce qui n’est pas compatible avec le besoin (besoin imaginaire, c’est un exercice, mais qui se veut quand même réaliste)... De surcroit, on pourra implémenter un système de cache optimisant les lectures pour tout item déjà lu, modifié ou ajouté.

    Avant de creuser du coté des designs patterns, voilà ce que j'aurais sans doute codé, dans les grandes lignes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    private class ConfigurationManagerItem
    {
    	public string Section { get; set; }
    	public string Parametre { get; set; }
    	public string Valeur { get; set; }
    }
    
    public abstract class ConfigurationManager
    {
    	// Cache
    	private List<ConfigurationManagerItem> _listeItem = new List<ConfigurationManagerItem>();
    	
    	private ConfigurationManagerItem Find( string section, string parametre )
    	{
    		return _listeItem
    				.Where( x => 0 == string.Compare(x.Section, section, true) )
    				.Where( x => 0 == string.Compare(x.Parametre, parametre, true) )
    				.FirstOrDefault();
    	}
    	
    	public string Get( string section, string parametre)
    	{
    		var item = Find( section, parametre);
    		
    		if( null == item ) 
    		{
    			item = new ConfigurationManagerItem() {
    				Section = section,
    				Parametre = parametre,
    				Valeur = Read( section, parametre )
    			};
    			_listeItem.Add( item );
    		}
    		
    		return item.Valeur;
    	}
    	
    	public void Set( string section, string parametre, string valeur )
    	{
    		var item = Find( section, parametre);
    		
    		if( ( null != item ) && ( 0 == string.Compare( item.Valeur, valeur ) ) ) return; // Rien à faire
    		
    		Write( section, parametre, valeur );
    		
    		_listeItem.Add(
    			new ConfigurationManagerItem() {
    				Section = section,
    				Parametre = parametre,
    				Valeur = valeur		
    			}
    		);
    	}
    
    	// Lecture, écriture, et suppression physique
    	protected abstract string Read( string section, string parametre );
    	protected abstract void Write( string section, string parametre, string valeur );
    	protected abstract void Delete( string section );
    	protected abstract void Delete( string section, string parametre);
    }
    
    public class ConfigurationManagerInstance
    {
    	private static ConfigurationManager _cm = null;
    	
    	public static ConfigurationManager Config
    	{
    		get
    		{
    			if( null == _cm ) throw new Exception("Aucune instance créée. Utilisez prélablement l'une des méthode CreateInstanceXXX()");
    			return _cm;
    		}
    	}
    	
    	public static ConfigurationManager CreateInstanceINI( string nomCompletFichierINI )
    	{
    		if( null != _cm ) throw new Exception("Une instance existe déjà");
    		return (_cm = new ConfigurationManagerINI( nomCompletFichierINI ) );
    	}
    	
    	public static ConfigurationManager CreateInstanceXML( string nomCompletFichierXML )
    	{
    		if( null != _cm ) throw new Exception("Une instance existe déjà");
    		return (_cm = new ConfigurationManagerXML( nomCompletFichierXML ) );
    	}
    }
    
    public class ConfigurationManagerINI : ConfigurationManager
    {
    	private string _nomCompletfichierINI = null;
    	
    	protected override string Read( string section, string parametre )
    	{
    		// TODO
    		return "";
    	}
    	protected override void Write( string section, string parametre, string valeur ) 
    	{
    		// TODO
    	}
    	protected override void Delete( string section )
    	{
    		// TODO
    	}
    	protected override void Delete( string section, string parametre)
    	{
    		// TODO
    	}
    	
    	public ConfigurationManagerINI( string nomCompletFichierINI )
    	{
    		_nomCompletfichierINI = nomCompletFichierINI;
    	}
    }
    
    public class ConfigurationManagerXML : ConfigurationManager
    {
    	private string _nomCompletfichierXML = null;
    	
    	protected override string Read( string section, string parametre )
    	{
    		// TODO
    		return "";
    	}
    	protected override void Write( string section, string parametre, string valeur ) 
    	{
    		// TODO
    	}
    	protected override void Delete( string section )
    	{
    		// TODO
    	}
    	protected override void Delete( string section, string parametre)
    	{
    		// TODO
    	}
    	
    	public ConfigurationManagerXML( string nomCompletFichierXML )
    	{
    		_nomCompletfichierXML = nomCompletFichierXML;
    	}
    }
    En n'utilisant rien d'autre que l'héritage, le résultat est assez propre, mais j'ai le sentiment que les designs patterns peuvent apporter quelque chose à ce code,
    par exemple l'ajout de la "stratégie base de données" (stockage de la configuration via un SGBD) obligerait à créer une nouvelle classe dérivée (rien de plus normal)
    mais aussi à modifier le code de la classe ConfigurationManagerInstance en ajoutant une nouvelle methode statique CreateInstanceDB( string connectionString )

    Bref pour toute nouvelle "strategie", il faudrait créer une classe ConfigurationManagerXXX dérivée de ConfigurationManager ET une méthode CreateInstanceXXX avec les mêmes parametres
    que ceux nécessaires au constructeur de ConfigurationManagerXXX

    L'idée serait de permettre la prise en charge de nouveaux modes de persistance sans impacter sur le code de ConfigurationManagerInstance

    En creusant du coté des designs patterns, je suis tombé sur Strategy et en première lecture il m'a semblé que ce pattern pouvait constituer une bonne réponse ?
    Cependant les différents tutoriels que j'ai trouvé içi ou là sur le web, ne mettent en oeuvre ce pattern que dans des cas "simples", où grosso-modo la stratégie
    n'est finalement qu'une méthode (procédure, fonction) et non un ensemble de fonctions (Get(), Set(), Delete())
    Typiquement, j'ai trouvé du code C# et Java où il était question de fournir une fonction permettant de comparer deux items, fonction passée en argument à
    une méthode Sort() d'une collection... Très classique... Bref, un peu comme monsieur Jourdain, j'ai utilisé le pattern Strategy sans le savoir ;-)

    Dans le bout de code que tu me donnes, quel(s) objet(s) ou type(s) d'objet(s) contiennent le code des méthodes Get() ? Set() ? Delete() ?
    Personnelement, je les verrais bien dans IPersistenceStrategy *et* dans ConfigurationManager, sachant que ConfigurationManager ferait simplement
    appel aux méthodes de mêmes noms de IPersistenceStrategy; bref on aurait un ConfigurationManager.Get() qui ferait un return PersistenceStrategy.Get() ?

    Auquel cas, ne retrouve-t-on pas mon IConfigurationManager ?

    Au niveau du code utilisateur, avec mon approche "orientée objet et héritage", on aurait quelquechose comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    ...
    ConfigurationManagerInstance.CreateInstanceINI("C:\\TEST.INI");
    ...
    var valeur = ConfigurationManagerInstance.Config.Get( "MaSection", "MonParametre" );
    ...
    Afin de mieux comprendre ta réponse en m'aidant à visualiser les choses, peux-tu me montrer ce que cela donnerait avec ton code ?

  4. #4
    Membre émérite
    Inscrit en
    Janvier 2011
    Messages
    805
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Janvier 2011
    Messages : 805
    Points : 2 918
    Points
    2 918
    Par défaut
    Beaucoup de choses à dire. D'abord je trouve que ton implémentation "naïve" tient la route et je la préfère de loin à un Manager monolithique dans lequel une forêt de if ou de switch gèrerait les différents types de persistance.

    Citation Envoyé par Isidore.76 Voir le message
    Dans le bout de code que tu me donnes, quel(s) objet(s) ou type(s) d'objet(s) contiennent le code des méthodes Get() ? Set() ? Delete() ?
    C'est d'une part le Manager qui propose ces méthodes puisque d'autres objets vont le solliciter pour faire ces opérations sur la configuration. Et d'autre part la classe Strategy peut contenir des méthodes nommées ainsi, mais derrière il y aura l'opération réelle de stockage, de lecture ou de suppression d'un élément de configuration.

    Citation Envoyé par Isidore.76 Voir le message
    Personnelement, je les verrais bien dans IPersistenceStrategy *et* dans ConfigurationManager, sachant que ConfigurationManager ferait simplement
    appel aux méthodes de mêmes noms de IPersistenceStrategy; bref on aurait un ConfigurationManager.Get() qui ferait un return PersistenceStrategy.Get() ?
    Tout à fait, mais il ne ferait pas que ça : le Manager n'est pas uniquement un passe-plat. Comme tu l'as évoqué il peut aussi contenir un cache, il pourrait gérer l'aspect accès concurrents ou versioning de la conf, que sais-je... L'important est de se souvenir que sa responsabilité est de fournir un point d'accès à la configuration aux autres objets qui le demandent.

    Voici ce que ça pourrait donner grosso modo avec un pattern Strategy :

    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
      public class ConfigurationManager
      {
        private static ConfigurationManager       _instance;
        private IConfigurationPersistenceStrategy _persistenceStrategy;
        private List<ConfigurationItem>           _listeItem            = new List<ConfigurationItem>(); // Cache
     
        public static ConfigurationManager        Instance // Attention singleton non thread-safe
        {
          get
          {
            if (_instance == null)
            {
              throw new Exception("Manager non initialisé");
            }
            return _instance;
          }
        }
     
        public void Initialize(IConfigurationPersistenceStrategy persistenceStrategy) // injection de la stratégie par initialiseur
        {
          _instance = new ConfigurationManager() { _persistenceStrategy = persistenceStrategy };
        }
     
        private ConfigurationItem Find(string section, string parametre)
        {
          return _listeItem
              .Where(x => 0 == string.Compare(x.Section, section, true))
              .Where(x => 0 == string.Compare(x.Parametre, parametre, true))
              .FirstOrDefault();
        }
     
        public string Get(string section, string parametre)
        {
          var item = Find(section, parametre);
     
          if (null == item)
          {
            item = new ConfigurationItem()
            {
              Section   = section,
              Parametre = parametre,
              Valeur    = _persistenceStrategy.Read(section, parametre)
            };
     
            _listeItem.Add(item);
          }
     
          return item.Valeur;
        }
     
        public void Set(string section, string parametre, string valeur)
        {
          var item = Find(section, parametre);
     
          if ((null != item) && (0 == string.Compare(item.Valeur, valeur))) return; // Rien à faire
     
          _persistenceStrategy.Write(section, parametre, valeur);
     
          _listeItem.Add(
            new ConfigurationItem()
            {
              Section   = section,
              Parametre = parametre,
              Valeur    = valeur
            }
          );
        }
     
        public void Delete(string section)
        {
          _persistenceStrategy.Delete(section);
     
          // TODO : logique de suppression du cache de tous les paramètres de la section
        }
     
        public void Delete(string section, string parametre)
        {
          _persistenceStrategy.Delete(section, parametre);
     
          var item = Find(section, parametre);
     
          if ((null != item))
          {
            _listeItem.Remove(item);
          }
        }
      }
     
      public interface IConfigurationPersistenceStrategy
      {
        string Read(string section, string parametre);
        void   Write(string section, string parametre, string valeur);
        void   Delete(string section);
        void   Delete(string section, string parametre);
      }
     
      public class ConfigurationPersistenceStrategyINI : IConfigurationPersistenceStrategy // Un exemple de stratégie
      {
     
        public string Read(string section, string parametre)
        {
          // TODO
          return string.Empty;
        }
     
        public void Write(string section, string parametre, string valeur)
        {
          // TODO
        }
     
        public void Delete(string section)
        {
          // TODO
        }
     
        public void Delete(string section, string parametre)
        {
          // TODO
        }
      }
     
      public class ConfigurationItem
      {
        public string Section   { get; set; }
        public string Parametre { get; set; }
        public string Valeur    { get; set; }
      }

    L'utilisation du pattern Strategy découle en fait de plusieurs "règles" de POO :

    • Préférer la composition à l'héritage. On obtient une séparation des responsabilités plus claire et un couplage plus faible en utilisant une composition/agrégation plutôt qu'une hiérarchie d'héritage. En l'occurrence cela consiste à dire que le manager de conf a une fonctionnalité de persistance externe plutôt que le manager de conf soit lui-même une fonctionnalité de persistance de par son héritage. Et lorsque cette stratégie de persistance est agrégée à notre objet sous la forme d'une interface ou d'une classe abstraite, c'est tout bénef, notre manager peut se reposer sur une abstraction de la manière de persister sans s'encombrer des détails du stockage. Le même manager peut alors changer de mode de persistance au runtime de manière transparente.

    • Single Responsibility Principle. En déportant le comportement de persistance dans une Stratégie à part, on sépare bien la responsabilité de fournir un point d'accès à la conf (ce qu'est précisément censé faire un ConfigurationManager) et la responsabilité de stocker physiquement cette configuration quelque part. D'ailleurs en termes de couches applicatives la première responsabilité serait sans doute au niveau d'une couche Services tandis que la seconde se place clairement dans la couche DAL.

      En séparant les prérogatives, SRP permet de renforcer la cohésion interne de chaque classe, c'est à dire sa propension à ne faire qu'une chose bien délimitée. Plus il y a de cohésion, plus les classes sont réutilisables facilement (qui n'a jamais été confronté au syndrome "j'utiliserais bien une instance de cette classe mais je vais devoir me coltiner les 90% de la classe qui n'ont rien à voir avec ce que je veux faire" ?) Et plus il y a de cohésion, moins une modification incorrecte dans la classe risque de lézarder le système avec des effets de bord sur un gros périmètre.

    • Open/Closed : ce principe dit que les classes devraient être ouvertes à l'extension mais fermées à la modification. En d'autres termes, il est préférable d'ajouter de nouvelles implémentations plutôt que de modifier les classes de base existantes car c'est beaucoup plus modulaire et moins dangereux. Ton implémentation ne respecte pas à 100% ce principe car à chaque fois qu'on veut ajouter un mode de stockage de la configuration, il faut modifier ConfigurationManagerInstance pour lui rajouter une méthode CreateInstanceXXX(), alors qu'avec l'implémentation Stratégie il suffit de créer une nouvelle implémentation concrète de IConfigurationPersistenceStrategy et de la passer à l'initialisation du Manager.

      La différence peut paraitre minime mais le fait que ça soit le code consommateur du Manager et lui seul qui connaisse l'implémentation de IConfigurationPersistenceStrategy avec lequel le Manager va être initialisé a son importance. Par exemple si dans le cadre de tests unitaires tu souhaites créer un mode de stockage factice pour avoir des tests en isolation, il te suffira dans tes tests d'injecter au Manager une ConfigurationPersistenceStrategyForTesting, implémentation concrète qui fait partie de ton projet de tests, alors qu'avec l'autre système tu aurais eu à rajouter un CreateInstanceForTesting() qui n'aurait pas vraiment sa place dans la classe ConfigurationManagerInstance car elle va aller en prod.


    SRP et Open/Closed sont les deux premiers principes du développement SOLID. Si tu parles anglais et que tu es curieux d'en apprendre plus sur ce qu'est au fond l'orienté objet, je te conseille vivement cette Viddler.com - BobMartin_SOLID_Design_Trimmed - Uploaded by RoyOsherove@@AMEPARAM@@http://www.viddler.com/player/d1c05245/@@AMEPARAM@@d1c05245/ de Uncle Bob Martin (auteur de Clean Code) sur le sujet.

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Août 2008
    Messages
    62
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 62
    Points : 59
    Points
    59
    Par défaut
    Merci pour tes explications et exemples de code qui permettent d’y voir tout à fait clair s’agissant du pattern strategy .

    J’ai donc modifié mon code en conséquence et l’ajoute à la fin de ma réponse. Peut-être y trouveras-tu quelques imperfections ?

    Pour ma part, je n’en suis pas tout à fait satisfait… Pour simplifier les choses, j’ai souhaité que la casse soit sans importance pour le nom de la section ou du parametre; idem pour les espaces en début ou fin de chaine; aussi ai-je d’abord validé puis converti en conséquence les arguments correspondants passés aux méthodes Get() Set() et Delete() de la classe ConfigurationManager; bien entendu cela devrait fonctionner, mais si pour pousser l’exercice un peu plus loin il fallait éventuellement être sensible à la casse, mon code serait largement à revoir : en effet, comment faire en sorte que le cache soit tantôt "case sensitive", tantôt "case insensitive" ? Gérer une stratégie supplémentaire dans ConfigurationManager ? Ou supprimer le système de cache de ConfigurationManager et laisser toute liberté à la stratégie de gérer elle-même son propre cache, sans d’ailleurs [pouvoir] l’y contraindre ? Cette dernière solution, sans doute plus simple, obligerait cependant à écrire "plusieurs fois" du code souvent très semblable chaque fois qu’on décidera de gérer un cache dans une stratégie…

    Egalement, il ne me semble pas que ce petit système de classes soit très ouvert à d’autres évolutions… L’impact, par exemple, serait assez lourd si, demain, la clef de chaque item n’était plus (section, parametre) mais (profil, section, parametre)… Afin de limiter l’ampleur des modifications, et s’agissant des arguments des méthodes, penses-tu qu’il serait avantageux de ne plus écrire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void Unefonction( string section, string parametre, string valeur )
    Mais plutôt

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void UneFonction( ConfigurationItem item )
    A priori, s’il faut ajouter la notion de profil dans ConfigurationItem, le code sera moins impacté; en outre les méthodes des classes n’auront pas une liste "à rallonge" de parametres, ce qui n’est sans doute pas mauvais question lisibilité…?

    Mais alors, que faut-il passer comme parametre en lieu et place d’un Get( [string profil,] string section, string parametre ) ? Un Get( ConfigurationItemKey key ) ? Admettons… ConfigurationItem dériverait donc de ConfigurationItemKey et ajouterait une propriété Valeur. Peut-être un poil compliqué, mais ça reste lisible ! Et pour Delete() ? C’est que l’on peut imaginer bon nombre de variantes pour Delete() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Delete( string profil)
    Delete( string profil, string section )
    Delete( string profil, string section, string parametre )
    Mais aussi :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Delete(string section ) // Tout profil
    Delete(string section, string parametre ) // Tout profil
    On ne peut, tout de même, créer autant de classes que de listes de parametres !? Faut-il abandonner cette piste et tout laisser en string ? Ou faire du "cas par cas", mais la cohérence du code pourrait en souffrir ?

    Enfin, pour en revenir au cache, s’il doit être géré (codé) dans chaque strategy, alors l’ajout du profil dans la clef impacte sur chaque strategy, y compris donc au niveau de son système de cache, ce qui est plus lourd que si le cache est géré dans le seul ConfigurationManager, ou peut-être ailleurs, pourvu que ce soit à un seul endroit…

    Vois-tu une élégante solution à ces problèmes ?

    Encore merci, vraiment, pour tes réponses de qualité !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    /// <summary>
    /// Item de configuration
    /// </summary>
    internal class ConfigurationItem
    {
    	public string Section;
    	public string Parametre;
    	public string Valeur;
    }
    
    /// <summary>
    /// Cache pour les items de configuration
    /// </summary>
    internal class ConfigurationCache
    {
    	private List<ConfigurationItem> _listeItem = new List<ConfigurationItem>();
    	
    	/// <summary>
    	/// Retourne un item du cache à partir de sa clef
    	/// Retourne null si non trouvé
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	/// <returns></returns>
    	public ConfigurationItem Find( string section, string parametre )
    	{
    		return _listeItem
    			.Where(x => 0 == string.Compare(x.Section, section))
    			.Where(x => 0 == string.Compare(x.Parametre, parametre))
    			.FirstOrDefault();
    	}
    	
    	/// <summary>
    	/// Supprime tous les items d'une section
    	/// </summary>
    	/// <param name="section"></param>
    	public void Remove( string section )
    	{
    		foreach( var item in _listeItem.Where(x => 0 == string.Compare(x.Section, section)).ToList() )
    		{
    			_listeItem.Remove( item );
    		}
    	}
    	
    	/// <summary>
    	/// Supprime un item 
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	public void Remove( string section, string parametre )
    	{
    		var item = this.Find( section, parametre );
    		if( item != null ) _listeItem.Remove( item );
    	}
    	
    	/// <summary>
    	/// Ajout ou modification d'un item
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	/// <param name="valeur"></param>
    	public void Save( string section, string parametre, string valeur )
    	{
    		ConfigurationItem item = Find( section, parametre );
    		
    		if( null == item )
    		{
    			_listeItem.Add( item = new ConfigurationItem() { Section = section, Parametre = parametre } );
    		}
    		
    		item.Valeur = valeur;
    	}
    }
    
    /// <summary>
    /// Opérations physiques de lecture / écriture / suppression dans la "source de données" stockant 
    /// les items de configuration
    /// </summary>
    public interface IConfigurationPersistenceStrategy
    {
    	/// <summary>
    	/// Retourne la valeur d'un item de la configuration
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	/// <returns></returns>
    	string Read(string section, string parametre);
    	
    	/// <summary>
    	/// Sauvagarde (création ou modification) d'un item
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	/// <param name="valeur"></param>
    	void Write(string section, string parametre, string valeur);
    	
    	/// <summary>
    	/// Suppression d'une section complète
    	/// </summary>
    	/// <param name="section"></param>
    	void Delete(string section);
    	
    	/// <summary>
    	/// Suppression d'un item
    	/// </summary>
    	void Delete(string section, string parametre);
    }
    
    /// <summary>
    /// Classe de gestion de configuration
    /// On appelle configuration un ensemble d'items où chaque item :
    /// 	- possède une valeur 
    /// 	- est identifié par un nom de section et un nom de parametre
    /// </summary>
    /// <remarks>
    /// Parametre et section ne sont pas sensibles à la casse (car convertis en minuscules)
    /// De même les espaces en début et fin de chaine sont ignorés
    /// </remarks>
    public class ConfigurationManager
    {
    	// Cache
    	private ConfigurationCache _cache = new ConfigurationCache();
    	// Unique instance
    	private static ConfigurationManager _instance;
    	// Stratégie de persistence (stockage)
    	private IConfigurationPersistenceStrategy _persistenceStrategy;
    
    	/// <summary>
    	/// Obtenir l'instance
    	/// </summary>
    	public static ConfigurationManager Instance // Attention singleton non thread-safe
    	{
    		get
    		{
    			if (_instance == null)
    			{
    				throw new Exception("Manager non initialisé");
    			}
    			return _instance;
    		}
    	}
    	
    	/// <summary>
    	/// Permet d'indiquer la stratégie de persistence à utiliser
    	/// (Injection de la stratégie par initialiseur)
    	/// </summary>
    	/// <param name="persistenceStrategy"></param>
    	public void Initialize(IConfigurationPersistenceStrategy persistenceStrategy)
    	{
    		_instance = new ConfigurationManager() { _persistenceStrategy = persistenceStrategy };
    	}
    	
    	/// <summary>
    	/// Valide et formate un nom de section
    	/// </summary>
    	private string FormatSection( string section )
    	{
    		if( null == section ) throw new Exception("La section ne peut être null");
    		if( 0 == section.Trim().Length ) throw new Exception("La section ne peut être vide");
    		return section.Trim().ToLower();
    	}
    	
    	/// <summary>
    	/// Valide et formate un nom de parametre 
    	/// </summary>
    	private string FormatParametre( string parametre )
    	{
    		if( null == parametre ) throw new Exception("Le parametre ne peut être null");
    		if( 0 == parametre.Trim().Length ) throw new Exception("Le parametre ne peut être vide");
    		return parametre.Trim().ToLower();
    	}
    	
    	/// <summary>
    	/// Valide et formate une valeur
    	/// </summary>
    	private string FormatValeur( string valeur )
    	{
    		if( null == valeur ) throw new Exception("La valeur ne peut être null");
    		return valeur;
    	}
    
    	/// <summary>
    	/// Retourne un item avec recherche préalable dans le cache
    	/// </summary>
    	public string Get(string section, string parametre)
    	{
    		// Validation et formatage
    		section = this.FormatSection(section);
    		parametre = this.FormatParametre(parametre);
    		
    		// Consultation du cache
    		var item = _cache.Find(section, parametre);
    		
    		// Nouvel item
    		if (null == item)
    		{
    			item = new ConfigurationItem() {
    				Section   = section,
    				Parametre = parametre,
    				Valeur    = _persistenceStrategy.Read(section, parametre)
    				};
    	
    			_cache.Save(item.Section, item.Parametre, item.Valeur);
    		}
    
    		return item.Valeur;
    	}
    
    	/// <summary>
    	/// Sauvegarde d'un item
    	/// </summary>
    	/// <param name="section"></param>
    	/// <param name="parametre"></param>
    	/// <param name="valeur"></param>
    	public void Set(string section, string parametre, string valeur)
    	{
    		// Validation et formatage
    		section = this.FormatSection(section);
    		parametre = this.FormatParametre(parametre);
    		
    		// Recherche dans le cache
    		var item = _cache.Find(section, parametre);
    		
    		// Trouvé dans le cache, et valeur inchangée ==> rien à faire
    		if ((null != item) && (0 == string.Compare(item.Valeur, valeur))) return; 
    		
    		// Ecriture physique
    		_persistenceStrategy.Write(section, parametre, valeur);
    		
    		// Mise à jour du cache
    		_cache.Save(item.Section, item.Parametre, item.Valeur);
    	}
    
    	/// <summary>
    	/// Suppression d'une section complete
    	/// </summary>
    	public void Delete(string section)
    	{
    		// Validation et formatage
    		section = this.FormatSection(section);
    
    		// Suppression physique
    		_persistenceStrategy.Delete(section);
    		
    		// Mise à jour du cache
    		_cache.Remove( section );
    	}
    
    	public void Delete(string section, string parametre)
    	{
    		// Validation et formatage
    		section = this.FormatSection(section);
    		parametre = this.FormatParametre(parametre);
    		
    		// Suppression physique
    		_persistenceStrategy.Delete(section, parametre);
    	
    		// Mise à jour du cache
    		_cache.Remove( section );
    	}
    }
    
    public class ConfigurationPersistenceStrategyINI : IConfigurationPersistenceStrategy // Un exemple de stratégie
    {
    	public string Read(string section, string parametre)
    	{
    	  // TODO
    	  return string.Empty;
    	}
    	public void Write(string section, string parametre, string valeur)
    	{
    		// TODO
    	}
    
    	public void Delete(string section)
    	{
    		// TODO
    	}
    
    	public void Delete(string section, string parametre)
    	{
    		// TODO
    	}
    }

  6. #6
    Membre émérite
    Inscrit en
    Janvier 2011
    Messages
    805
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Janvier 2011
    Messages : 805
    Points : 2 918
    Points
    2 918
    Par défaut
    Citation Envoyé par Isidore.76 Voir le message
    penses-tu qu’il serait avantageux de ne plus écrire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void Unefonction( string section, string parametre, string valeur )
    Mais plutôt

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void UneFonction( ConfigurationItem item )
    Oui. En fait tu viens de sentir un code smell appelé Primitive Obsession. Lorsqu'un type de données est suffisamment complexe, on devrait toujours créer une classe pour le représenter plutôt que manipuler des variables de type primitif (int, string...)

    Outre la plus grande concision que tu as notée, cela permet aussi d'être type safe (vérification à la compilation du type de données utilisé) et de faire apparaitre une notion forte du domaine métier dans sa classe à elle donc meilleure expressivité du code. Il n'y a pas de raison pour que la classe ConfigurationItem que tu as créée reste visible du manager uniquement, c'est à mon avis ce type d'item que devrait manipuler tout code qui veut accéder à la configuration.

    Concernant l'évolutivité de la structure de la configuration, tout est imaginable mais je vois 2 possibilités :

    • Fixer une structure arbitraire avec un nombre de niveaux limité voire un certain nombre de sections bien identifiées.
    • Créer une structure dynamique avec un nombre illimité de sections/sous-sections et d'items.


    Le choix va dépendre du contexte de la fonctionnalité et des besoins. L'idée c'est aussi comme je le disais de préférer le plus simple qui fonctionne et de ne pas construire une centrale atomique pour faire marcher une ampoule.

Discussions similaires

  1. Dépendance entre objets : quel design pattern?
    Par zigxag dans le forum Design Patterns
    Réponses: 3
    Dernier message: 13/12/2007, 10h14
  2. Quel Design Pattern choisir?
    Par Aïssa dans le forum Design Patterns
    Réponses: 20
    Dernier message: 24/01/2007, 17h12
  3. Réponses: 9
    Dernier message: 05/12/2006, 10h00
  4. Réponses: 5
    Dernier message: 21/06/2006, 14h47
  5. Quel design pattern pour réaliser une synthèse
    Par jbwan dans le forum Design Patterns
    Réponses: 3
    Dernier message: 21/04/2006, 12h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo