L’Abstract Factory c’est bien, l’utiliser, c’est plus compliqué
13 March 2013
Je me suis retrouvé récemment face à un problème assez connu. Ce problème consistait à créer, par exemple, différents types d’animaux en fonction d’un discriminant, comme le type d’animal dans notre exemple. La création n’étant, dans mon problème, pas triviale, il est assez naturel de faire appel à une Factory pour cela. Mieux ! Une Abstract Factory permet de masquer la complexité de la création de chaque type d’animal, en ne laissant à l’appelant qu’un appel simple du style :
Animal myAnimal = myFactory.CreateAnimal(name, age);
Simple avez-vous dit ? Pas tout à fait, car « a’men donné », comme on dit dans le milieu rugbystique, il va bien falloir instancier « myFactory ». C’est là tout l’enjeu de ce billet.
Rappel sur l’Abstract Factory
Je dirais même bref rappel. Voilà une image qui résume à elle seule ce qu’est une abstract factory :
Pour en savoir plus, j’ai trouvé ici un meta-lien exceptionnel : http://past.is/d1ZR.
Voilà ! J’espère que vous vous êtes rafraîchi la mémoire. Passons à la suite…
Consommer l’Abstract Factory
Les articles que vous venez peut-être de lire pour vous rafraîchir la mémoire vous ont semblé assez basique. Si vous les relisez, vous remarquerez que l’instanciation des factories se fait dans un bon vieux « main » des familles. Entendons-nous bien : l’Abstract Factory est une forme d’inversion de dépendance. Plutôt que de dépendre de plusieurs factories identiques, qui ont une responsabilité identique (à savoir créer un Animal), on préfère ne dépendre que de leur abstraction.
On cherche donc bien une forme de découplage entre l’appelant et les factories en l’occurrence. Pour ce qui est des exemples proposés, donc, c’est complètement raté, puisque dans l’appelant, vous devez connaitre l’ensemble des implémentations possibles, et les conditions nécessaires pour utiliser une implémentation ou une autre. Bref, le problème reste entier. A ce stade, je vois au moins deux solutions :
- La Factory Method
- Et la meta-factory
Factory Method
La première solution qui m’a été suggérée c’est ni plus ni moins qu’une factory method. Cette méthode a pour responsabilité de déterminer quelle implémentation de l’abstract factory est la bonne, en fonction de paramètres divers, et d’en retourner une instance. Pour plus de « commodité » (mais aussi pour éviter de déborder sur le point suivant et casser mon billet), cette méthode est accessible depuis l’abstract factory, et donc de manière statique (puisque la factory est abstraite :). Ce qui revient, dans notre exemple à :
AnimalFactory myFactory = AnimalFactory.GetAnimalFactory(animalType); Animal myAnimal = myFactory.CreateAnimal(name, age);
C’est parfait n’est-ce pas ? Et bien non, en fait… Moi, ça ne me plait pas du tout. Je vous disais que l’abstract factory était là pour nous permettre un découplage. Or l’appel statique nous recrée un couplage fort : il n’y a aucun moyen de s’en défaire, notamment pour rendre le code appelant facilement testable. Finalement on n’a fait que déporter le problème… Voyons donc une autre solution…
La meta-factory
La meta factory c’est cette idée que puisqu’une factory est l’artefact idéal à qui déléguer la responsabilité d’une création, autant appliquer l’idée jusqu’au bout, et notamment pour notre abstract factory. La factory d’une factory devenant ainsi une meta-factory ! J’avoue que l’idée même qu’un tel type d’artefact puisse exister me met en alerte d’un risque d’overdesign imminent ! Mais soit, si nous allons au bout de l’idée, du point de vue de l’appelant, cela donne ceci :
MetaAnimalFactory metaFactory = new MetaAnimalFactory(); // qui peut être injecté AnimalFactory myFactory = metaFactory.GetAnimalFactory(animalType); Animal myAnimal = myFactory.CreateAnimal(name, age);
Vu comme ça, cela parait faire un grand nombre de choses à savoir du point de vue de l’appelant. Notez quand même que nous ne sommes plus dans le cadre d’un appel statique, puisqu’un appel statique, c’est mal :). Notez aussi que le « new » effectué sur la meta-factory ne vaut pas mieux, sauf qu’en l’occurrence, on va pouvoir l’injecter dans l’appelant, en établissant une relation de dépendance entre l’appelant et cette meta-factory. Qui plus est, la méthode exposée a un comportement qui s’apparente à un transaction script, ce qui nous conforte dans l’idée que MetaAnimalFactory peut être injecté dans le constructeur de notre appelant, ceci n’étant pas possible avec l’abstract factory. Si on fait le bilan, celui-ci n’est pas si mauvais. La seule chose qui pose problème c’est que le niveau d’abstraction n’est pas suffisant du point de vue de l’appelant, et aussi de savoir que cette factory est une « meta », et n’a donc aucun intérêt que celui de fournir la bonne implémentation. Mais ceci peut se corriger facilement, laissant la porte ouverte à une troisième voie….
La troisième voie (donc…)
L’idée de cette idée « ultime », si tant est qu’elle le soit, c’est de faire croire à l’appelant qu’il continue de collaborer avec une factory, et de faire en sorte que notre meta-factory agisse comme une factory normale. On va en réalité donner un peu plus de responsabilité à notre fameuse meta factory. Voici le diagramme ajusté :
Comme vous le voyez, on a fait apparaître une interface, en plus de l’abstract factory. L’appelant ne collabore plus avec l’abstract factory, mais avec cette interface. Autre changement : la meta-factory implémente cette nouvelle interface, agissant ainsi comme une vraie factory. Les implémentations de chaque méthode du contrat consiste donc à déterminer la bonne implémentation à utiliser, et à utiliser l’instance créée comme une instance de l’abstract factory. Du point de vue de l’appelant, cela donne ceci :
IAnimalFactory myFactory // injecté par constructeur Animal myAnimal = myFactory.CreateAnimal(name, age);
D’un point de vue de l’élégance de l’API, du découplage, et de la testabilité, on y gagne carrément, puisque l’appelant n’a même pas connaissance de la fameuse meta factory. Cela peut paraître un peu lourd en terme de nombre d’artefacts et de complexité, mais il faut bien imaginer une construction assez complexe justement, pour laquelle une abstract factory se justifie.
Une meta-quoi ?
J’entends d’ici les commentaires me disant : « Non, mais Jérôme, tu délires ? Ce dont tu parles ça a un nom et ça s’appelle : … » (avec le lien qui va avec merci !). Et j’en ai bien conscience ! Encore une fois cette idée a émergée sur un tableau. Oui, car même en faisant du TDD, on peut aussi lever la tête du guidon régulièrement et se poser quelques questions en posant des concepts sur un tableau. Et malheureusement, je n’ai pas trouvé d’info sur un pattern existant. Voilà pourquoi d’ailleurs je n’ai pas envie de lui donner de nom, car je pense évidemment que quelqu’un l’a fait avant. Donc, vous pouvez m’aider en m’indiquant des ressources (assez fiables) sur l’existence d’un pattern de ce type, et je ne manquerai pas d’éditer ce post pour les mentionner. J’avoue en effet que ce concept de « meta » m’est assez désagréable 🙂
N’hésitez pas non plus à critiquer cette approche, autant que vous le souhaitez. Je ne prétends pas détenir la vérité. Si vous avez une meilleure idée, partagez-la !
Donc à vos commentaires ! 🙂