PROFDINFO.COM

Votre enseignant d'informatique en ligne

Les patrons de conception

Les patrons de type Création

Ces patrons s'occupent de créer et d'initialiser des objets de façon structurée (et souvent de façon à cacher une complexité ou à déléguer une partie de la création d'une classe à une autre). Nous verrons aujourd'hui 3 des 4 patrons de Création du document de Joan-Sébastien Morales.

L'usine abstraite (Abstract Factory)

Une usine abstraite ajoute un niveau d’abstraction pour la création d’objets de telle sorte que le client n’a pas besoin de connaître les classes concrètes des objets.

En effet, l'usine est abstraite et contient des méthodes permettant de retourner des objets d'un type abstrait.

Si tout y est abstrait, à quoi ça sert? Bien entendu, pour s'en servir il faut également créer des classes concrètes qui dérivent de ces classes abstraites. On aura donc des usines concrètes (enfants de l'usine abstraites) et des objets concrets (enfin des objets abstraits). Lorsque l'on appelle une méthode de l'usine, celle-ci nous retourne une instance d'un objet.

L'utilisateur peut donc se déclarer un objet de type UsineAbstraite, qu'il fera pointer sur l'usine concrète de son choix. Il appellera ensuite une méthode de son usine afin de recevoir un objet du type voulu - en fait un objet descendant du type voulu, donc implémentant toutes ses méthodes.

Ainsi, non seulement on a une totale abstraction du type, mais l'utilisateur ne sait même pas lui-même quel type il va recevoir (ou du moins il n'a pas besoin de le savoir)! Ce qui compte c'est que ce qu'il reçoit hérite d'un certain type abstrait connu d'avance et donc implémentera certaines méthodes utiles pour lui.

Dans l'exemple du document de Joan, on a une usine (ContinentFactory) qui crée (et retourne) des herbivores et des carnivores - tout cela est abstrait. On a des herbivores (wildebeests (gnous) et bisons) ainsi que des carnivores (lions et loups), qui peuvent manger des herbivores.

L'usine concrète "AfricaFactory" retourne des lions et des gnous, tandis que l'usine concrète "AmericaFactory" retourne des loups et des bisons.

L'utilisateur se déclare une usine, qu'il fait pointer vers l'usine concrète qu'il veut. Tout le code reste fonctionnel, peu importe quelle usine il choisit, alors que les objets peuvent très bien avoir des comportements différents.

Le Constructeur (Builder)

À ne pas confondre avec un constructeur, cette méthode que toute classe contient et qui permet d'en créer des instances!

Le Constructeur est un patron qui sépare la construction d'un objet complexe de sa représentation. Autrement dit, on a une classe qui représente un objet complexe et on a une autre classe qui sait comment "construire" (et initialiser) cet objet complexe. En plus, on a un "Directeur", une classe à qui on peut passer un objet de type Constructeur et qui l'appellera aussitôt pour créer l'objet complexe. Évidemment, pour que tout ça fonctionne, il faut de l'abstraction!

Dans l'exemple de Joan, la classe abstraite VehicleBuilder contient une instance d'un Vehicle, une propriété permettant d'y accéder et des méthodes abstraites qui serviront à construire les différentes parties d'un Vehicle (ici la structure (Frame), le moteur (Engine), les roues (Wheels) et les portes (Doors)). Comme la classe est abstraite elle ne sait pas ce qu'elle construira comme véhicule et pourra être utiliser pour en construire de toutes sortes.

La classe Vehicle contient un type et les différentes parties du véhicule. Elle contient un indexeur permettant d'accéder aux parties du véhicule et une méthode (Show) qui affiche la description complète de ce véhicule.

Différentes spécialisations de VehicleBuilder permettent de construire différents types de véhicules: MotorcycleBuilder, CarBuilder et ScooterBuilder (pour construire des trottinettes). En overridant les méthodes abstraites de VehicleBuilder, elles peuvent créer un véhicule chacune à leur façon.

Finalement, la classe correspondant au "Directeur" est ici la classe "Shop" (littéralement, l'usine). La "Shop" contient une méthode Construct qui accepte un VehicleBuilder. Cette dernière appelle les différentes méthodes du VehicleBuilder pour construire un véhicule. Ce véhicule, interne au VehicleBuilder, pourra être différent à chaque fois.

Une remarque intéressante concernant cet exemple: les véhicules utilisent une Hashtable pour loger leurs différentes parties. Une table de hashage est un tableau qui est indexé par une clé quelconque, de n'importe quel type. La table de hashage stocke ses éléments quelque part dans le tableau, en fonction de leur clé. Une clé correspond toujours à une et une seule case du tableau et la façon dont cette correspondance est faite n'est pas importante pour l'utilisateur.

On peut accéder à une case d'une table de hashage aussi simplement qu'à une case d'un tableau. La seule différence est qu'on utilise une clé qui peut être de n'importe quel type, même à l'intérieur d'une même table de hashage. Par exemple, on peut faire simplement:

Hashtable table = new Hashtable();
table["uneClé"] = "2";
table[45] = "UnMot";

Console.WriteLine(table["uneClé"]);

Autrement dit, on met une valeur quelconque dans la table, en utilisant une clé quelconque dont on pourra se servir pour retrouver la valeur.

Dans l'exemple des Constructeurs de véhicules, cet usage est très pratique puisqu'il nous permet aisément de stocker une collection d'informations sur le véhicule, sans avoir besoin d'une correspondance particulière entre les numéros de cases et les informations elles-mêmes. Un indexeur dans la classe Vehicle permet d'accéder à ces informations encore plus aisément.

Le singleton

Le singleton est un patron qui s'assure que la classe qu'il représente n'a qu'une seule instance. De plus, il présente une façon standard d'y accéder.

Le singleton est souvent appelé "anti-pattern", puisqu'il contourne en fait le modèle objet afin de créer une espèce de "variable globale". Il est généralement utilisé pour y stocker des informations pertinentes à tout un système, accessible à plusieurs objets.

Le singleton est très simple à comprendre: c'est une classe qui offre une méthode statique permettant de retourner l'unique instance de la classe. Si une telle instance n'existe pas, elle la crée. Si l'instance existe, la méthode statique la retourne.

Pour être bien certain que personne d'autre ne puisse créer une autre instance de cette classe, le constructeur est déclaré comme private ou protected.

Dans l'exemple de Joan, le singleton est un "LoadBalancer", c'est-à-dire un équilibreur de charge utilisé sur un réseau. Lorsqu'on le lui demande, il nous donne le nom d'un serveur choisi au hasard - on peut supposer que c'est à ce serveur qu'on enverra une requête ce coup-ci.

Le singleton est utile puisqu'il permet de fournir des informations à plusieurs autres classes qui en ont besoin. Comme sa création est protégée, on saura que chaque objet qui aura besoin de l'information utilisera toujours le même - on sauve ainsi de la mémoire et on s'assure que tout le monde parle au même interlocuteur, ce qui peut être essentiel selon l'application.

La seule complexité du singleton est lorsqu'il est utilisé dans des applications multi-threads. En effet, lorsque plusieurs threads s'exécutent simultanément, chacun d'entre eux peut avoir sa "version" du singleton et à ce moment-ci le singleton n'en est plus un!

La précaution à prendre à ce moment: utiliser un Mutex afin de s'assurer qu'un seul thread ne puisse instancier le singleton. En effet, lorsque plusieurs processus tentent d'obtenir un mutex (avec la méthode WaitOne), un seul d'entre eux pourra en obtenir un. Les autres devront attendre tant que le mutex n'aura pas été relâché (avec la méthode Close).