Comment mettre en œuvre une solution robuste pour gérer les énumérations dans les applications à grande échelle
Dans le développement de logiciels, nous rencontrons souvent ce que l’on appelle des énumérations ou des énumérations. Cette structure est très utile lorsqu’il existe un groupe d’options ou de valeurs limitées liées les unes aux autres, car elle nous permet de gérer les valeurs de manière sécurisée :
Son principal avantage est la facilité qu’il apporte à la personnalisation de l’expérience utilisateur. Une simple instruction switch-case peut parcourir toutes les options et contrôler ce qui se passe dans chaque cas :
Malgré cette polyvalence et le fait qu’il s’agit généralement du premier choix pour traiter les énumérations, l’utilisation aveugle de la structure switch-case pose certains problèmes lors de la réflexion sur la maintenance à long terme du code, en particulier lorsqu’il s’agit d’applications volumineuses et robustes.
Dans cet article, je présente le modèle Abstract Factory comme alternative à l’utilisation de switch-case pour la gestion des énumérations. Cette approche correspond aux principes SOLID et, par conséquent, garantit une plus grande évolutivité et maintenabilité des systèmes.
Considérons une simple application iOS qui affiche des informations sur les pays – leur nom, leur drapeau et une courte description. Dans un premier temps, nous souhaitons présenter les options États-Unis, Canada et Mexique, comme indiqué ci-dessous :
Dans ce scénario, il existe une énumération nommée Country
qui définit toutes les options de pays disponibles :
L’application a été implémentée selon le modèle MVVM et son code peut être obtenu à partir de GitHuben vérifiant le fonctionnalité/branche initiale. Les ViewModel
couche contient la logique principale de définition des données et est définie comme :
Cette classe a un paramètre obligatoire dans sa méthode constructeur : le pays à présenter. Il dispose également de trois variables accessibles par la couche de vue : flag
, name
et description
. Ils sont implémentés via une structure switch-case, qui renvoie les valeurs correspondant au pays injecté. De cette façon, nous définissons une expérience spécifique pour chaque pays.
Il s’agit d’une approche très courante lorsque vous travaillez avec des énumérations, et la documentation officielle de Swift recommande son utilisation. Bien qu’il s’agisse d’une structure simple et pratique dans de nombreux cas, certains inconvénients doivent être pris en compte lors de la mise en œuvre de cette solution.
Supposons que dans une deuxième version de l’application, nous souhaitions inclure davantage de pays. De la façon dont c’est implémenté aujourd’hui, la création de nouveaux cas implique d’inclure les mêmes cas dans chacun des commutateurs — sinon, l’application ne se compile pas.
Le problème est que l’inclusion de plus en plus de cas rend les structures de commutation très longues et difficiles à lire. En fait, de nombreuses règles lint fixent une limite au nombre de cas dans une instruction switch pour des raisons de lisibilité. Alors que lorsque cet article a été écrit, il y avait 193 pays reconnus, il n’est pas difficile d’imaginer que ces structures peuvent devenir immenses à mesure que l’application se développe, ce qui rend l’application non évolutive.
Compte tenu des principes SOLID, il y a aussi quelques inconvénients. Le principe de responsabilité unique (SRP) stipule que chaque composant ou fonction ne doit avoir qu’une seule responsabilité ou raison de changement. Dans ce cas, le AfficherModèle les variables ont clairement plus d’une responsabilité, car elles contiennent des données de différents pays.
Et cela viole également le principe ouvert-fermé (OCP), puisque l’ajout de nouveaux pays nécessite de modifier la structure existante. En pensant à l’évolution de l’application, des modèles qui prennent en charge l’évolutivité du système sont souhaitables. Par conséquent, le modèle Abstract Factory peut être très utile ici.
Abstract Factory est un modèle de création qui permet la construction d’objets similaires via une interface, c’est-à-dire sans définir explicitement de classes concrètes.
Ce concept est important car il suit le principe d’inversion de dépendance (DIP), qui détermine que les applications doivent être orientées interface.
Il est défini un protocole appelé CountryAbstractFactory
, qui a une fonction pour chaque paramètre des pays. Le protocole est implémenté par les classes concrètes USACountryFactory
, CanadaCountryFactory
et MexicoCountryFactory
qui sont responsables de la création d’informations sur les États-Unis, le Canada et le Mexique respectivement.
Notez que dans ce cas, toutes les informations exposées par la fabrique sont une chaîne, mais des objets plus complexes peuvent également être utilisés si nécessaire. Pour exposer des classes personnalisées, il serait nécessaire de définir un protocole commun pour elles et de l’affecter comme type de retour de la fonction d’usine. Pour une compréhension plus approfondie, je vous recommande de lire le tutoriel sur le Site Web de RefactoringGuru.
Nous pouvons implémenter cela dans Swift selon le code suivant :
Après avoir implémenté les classes d’usine, il est possible de modifier le ViewModel
pour éliminer l’utilisation excessive de structures de boîtier de commutation, comme décrit ci-dessous. Ce code est accessible dans le référentiel en vérifiant le feature/abstract-factory bifurquer.
A ce moment, les variables de la ViewModel
ne présente plus les instructions switch, mais il y a toujours une structure switch-case située dans la variable calculée countryFactory
. Il définit quelle implémentation Factory doit être utilisée en fonction du pays.
C’est une meilleure approche que la précédente, car il n’y a maintenant qu’une seule structure de cas de commutation. Cela garantit un traitement centralisé des informations pays, favorisant la maintenabilité du code et garantissant la conformité des variables de données au SRP.
Cependant, dans les scénarios où il y a un grand nombre de pays disponibles, il n’est pas souhaitable d’avoir une instruction switch comme celle présente dans le countryFactory
. Dans ce cas, nous pouvons prévoir un petit ajustement pour éliminer complètement ce genre de structure.
Afin d’éliminer la dernière structure de boîtier de commutation et de donner une touche professionnelle à la mise en œuvre, nous pouvons ajouter une fonction supplémentaire au AbstractFactory
chargé de rattacher chaque cas à une usine spécifique :
Les canHandle
La fonction renvoie un booléen indiquant si cette classe est capable de gérer un pays donné ou non. De cette façon, il est possible de modifier le ViewModel
comme suit:
Une constante nommée registeredFactories
a été ajouté et il stocke un vecteur contenant toutes les classes Factory enregistrées. De cette façon, le countryFactory
La variable renvoie la première classe capable de gérer les données du pays respectif.
Afin d’éviter de manipuler des options, le USACountryFactory
a été utilisé par défaut. Cependant, il est possible de définir une classe de fabrique spécifique pour gérer le cas par défaut, qui devrait renvoyer quelque chose comme un message d’erreur – dans cet exemple, il n’a pas été implémenté pour simplifier l’explication.
Maintenant, lorsqu’il est nécessaire d’ajouter un nouveau pays, nous pouvons simplement créer une nouvelle usine mettant en œuvre le CountryAbstractFactory
protocole et enregistrer la classe dans le registeredFactories
déployer. De cette façon, le code devient adapté à OCP puisque les classes sont découplées, et l’inclusion de nouveaux objets n’impacte pas les éléments préexistants.
Note: Dans une application de production, je recommande fortement d’utiliser le modèle d’injection de dépendance pour injecter la classe d’usine sur la définition directement dans le ViewModel
. Ainsi, nous pourrions séparer la responsabilité de choisir le bon pays de la classe de modèle de vue favorisant l’écriture de tests unitaires — mais c’est un sujet pour un autre article.
Dans cet article, le modèle de conception Abstract Factory a été appliqué dans une application iOS afin de remplacer les structures switch-case pour la gestion des énumérations. En conséquence, nous avons obtenu une approche beaucoup plus robuste et évolutive, qui s’inscrit dans les principes SOLID et est assez intéressante pour les grandes applications dans lesquelles la croissance du système est envisagée.
Il est important de se rappeler que cette approche présente également certains inconvénients par rapport aux structures simples de cas de commutation, telles que sa complexité et sa courbe de mise en œuvre plus élevées. Dans les petits projets où il n’y a pas d’exigence explicite d’évolutivité, l’utilisation de ce modèle peut ne pas être nécessaire et même être considérée comme une ingénierie excessive, car la solution la plus simple résoudrait le problème sans effets secondaires imminents.
Enfin, il est toujours bon de souligner qu’aucun design ou modèle architectural n’est une « solution miracle » qui devrait être utilisée dans n’importe quel projet ou situation. Lors de la conception d’un logiciel, nous devons toujours évaluer tous les scénarios et prendre des décisions en toute connaissance de cause. Dans cet esprit, le modèle Abstract Factory est un outil très élégant et puissant qui peut être inclus dans notre arsenal de développement et utilisé pour améliorer nos projets.
Merci d’avoir lu! Le code source est disponible dans mon Référentiel GitHub.