Design Pattern Conception - 11 Apr 2017

Le Design Pattern 'Abstract Factory'

Xavier R
Écrit par Xavier R

Deuxième article d'une série consacrée aux Design Patterns. Aujourd'hui : le pattern Abstract Factory

Deuxième article d’une série consacrée aux Design Patterns. Aujourd’hui, le pattern AbstractFactory où il sera question de produits, de familles et de fabriques (factories, au pluriel s’il vous plaît).

Une gentille famille américaine
Une gentille famille américaine
Une famille américaine
Une gentille famille américaine
Le livre Head First Design Patterns (dont j'ai déjà vanté les mérites) regroupe les deux patterns Factory Method et AbstractFactory dans un même chapitre consultable en ligne et intitulé The Factory Pattern: Baking with OO Goodness. Je ne saurais trop vous encourager à le consulter !

Classification

Le pattern Abstract Factory est classé dans la catégorie des Design Patterns de création.

Définition

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

C’est une très jolie définition dont je ne me lasse pas … (Comme toute définition, elle paraît assez barbare tant que l’on n’a pas approfondi le concept, ce que je vous propose de faire dans les paragraphes qui suivent).

En résumé, le pattern Abstract Factory va nous permettre d’instancier des familles de produits dépendant les uns des autres sans qu’il soit nécessaire de préciser leur type concret (je ne suis pas sûr qu’on soit plus avancé …)

Schéma du design pattern Abstract Factory

Le Design Pattern 'Abstract Factory'

Ne vous laissez pas impressionner par la densité du schéma et le nombre de participants. Le pattern n’a rien d’insurmontable et peut s’avérer utile dans de nombreuses situations.

Pour l’heure, et pour y voir un peu plus clair, je vous suggère de diviser mentalement le schéma en deux :

Les produits appartiennent à une famille. Sur ce schéma le produit A1 et le produit B1 appartiennent à la même famille et sont donc destinés à collaborer ensemble. Le produit A2 et le produit B2 appartiennent à une autre famille et sont également conçus pour collaborer ensemble. En revanche, A1 et B2 ne peuvent pas fonctionner ensemble. Il est donc important d’instancier des produits qui sont compatibles (autrement dit, qui appartiennent à la même famille) et c’est là qu’interviennent les fabriques abstraites.

Je suis sûr qu’au cours de votre carrière de développeur vous avez été amenés à modéliser des contraintes métiers énoncées peu ou prou ainsi : “si mon produit est une instance de A, alors c’est mon service A qui le gère ; si mon produit est une instance de B, alors c’est mon service B qui le gère”. Ne cherchez pas plus loin, c’est justement à ce genre de problématique que répond le pattern Abstract Factory.

Exemple de problèmes familiaux …

J’ai développé une extension Chrome qui permet de présenter des statistiques structurées à partir du détail d’un commit sur Github : nom du projet, nom de l’auteur, liste des fichiers concernés par les modifications, nombre de lignes supprimées, nombre de lignes ajoutées, etc.

Pour ce faire, j’ai développé une librairie qui contient deux classes qui analysent le contenu HTML d’une page Github et en extraient les données pertinentes :

Mon client est aux anges et souhaite donc étendre ce fonctionnel aux projets hébergés sur Gitlab (vous la voyez arriver la nouvelle famille ?). Bien évidemment, la structure HTML des pages Gitlab est complètement différente des pages Github, et mon parser Github est tout à fait incapable de comprendre les données retournées par mon crawler Gitlab … La contrainte est donc la suivante : si mon crawler est un crawler Github, alors je dois utiliser le parser Github ; si mon crawler est un crawler Gitlab, alors je dois utiliser le parser Gitlab. Et mon client ne compte pas s’arrêter là, il souhaite bien évidemment aussi gérer les pages Bitbucket …

Résolution de la problématique à l’aide du DP AbstractFactory

Vous l’aurez sans doute deviné, nous nous trouvons en présence de trois familles de produits différentes : la famille des produits Github, la famille des produits Gitlab et la famille des produits Bickbucket. Dans chacune de ces familles, on retrouve un produit Crawler et un produit Parser conçus pour collaborer ensemble.

Comment garantir que j’utilise des produits d’une même famille ? Réponse : le pattern Abstract Factory. Ce qui nous donne le récapitulatif suivant :

  Github Gitlab BitBucket
SCMCrawlerInterface:
public function getProjectNameHtml();
public function getSummaryHtml();
public function getCommittedFilesHtml();
            
  GithubCrawler GitlabCrawler BitBucketCrawler
SCMParserInterface:
public function parseProjectName($projectNameHtml);
public function parseSummary($summaryHtml);
public function parseCommittedFiles($committedFilesHtml);
            
  GithubParser GitlabParser BitBucketParser
SCMFactoryInterface
public function getCrawler();
public function getParser();
            
  GithubFactory GitlabFactory BitBucketFactory

Explication de la solution

Voici à titre d’exemple le code de la fabrique GithubFactory (je vous épargne le code des deux autres fabriques):

    <?php
    class GithubFactory implements SCMFactoryInterface {
        public function getCrawler(): SCMCrawlerInterface {
            return new GithubCrawler();
        }
        public function getParser(): SCMParserInterface {
            return new GithubParser();
        }
    }

A présent, en fonction du contexte dans lequel nous nous trouvons (Github, Gitlab ou Bitbucket), il suffit d’instancier la bonne fabrique et d’appeler respectivement ses méthodes getCrawler et getParser pour obtenir les bons services adaptés au contexte courant. C’est au final la fabrique qui est garante de la compatibilité des produits qui collaborent.

Autre avantage de cette solution : la résolution des services à instancier selon le contexte se fait une seule fois, il suffit d’instancier la bonne fabrique (nous n’avons pas à résoudre l’instanciation du bon crawler dans un premier temps, puis du bon parser dans un second temps).

Grâce à ce pattern, l’ajout d’une nouvelle famille de produits peut également se faire assez aisément (un nouveau SCM à gérer), tout comme l’ajout d’un nouveau produit dans chaque famille (on pourrait envisager d’implémenter un Renderer spécialisé dans chaque famille, chargé de l’affichage des données obtenues, par exemple).

Conclusion

Parce que ce sont tous deux des patterns de fabrique, on confond souvent la Factory Method et le pattern Abstract Factory. Voici donc un résumé de ces deux patterns en mettant l’accent sur ce qui les différencie :