Introduction aux modèles de conception créative

1. Introduction

En génie logiciel, un modèle de conception décrit une solution établie aux problèmes les plus fréquemment rencontrés dans la conception de logiciels. Il représente les meilleures pratiques développées sur une longue période par essais et erreurs par des développeurs de logiciels expérimentés.

Design Patterns a gagné en popularité après que le livre Design Patterns: Elements of Reusable Object-Oriented Software a été publié en 1994 par Erich Gamma, John Vlissides, Ralph Johnson et Richard Helm (également connu sous le nom de Gang of Four ou GoF).

Dans cet article, nous explorerons les modèles de conception créative et leurs types. Nous examinerons également quelques exemples de code et discuterons des situations dans lesquelles ces modèles correspondent à notre conception.

2. Modèles de conception créative

Les modèles de conception créative concernent la manière dont les objets sont créés. Ils réduisent les complexités et l'instabilité en créant des objets de manière contrôlée.

Le nouvel opérateur est souvent considéré comme dangereux car il disperse des objets dans toute l'application. Au fil du temps, il peut devenir difficile de changer une implémentation car les classes deviennent étroitement couplées.

Les modèles de conception créatifs résolvent ce problème en dissociant entièrement le client du processus d'initialisation réel.

Dans cet article, nous aborderons quatre types de modèles de conception créative:

  1. Singleton - Garantit qu'au plus une seule instance d'un objet existe dans l'application
  2. Méthode d'usine - Crée des objets de plusieurs classes liées sans spécifier l'objet exact à créer
  3. Abstract Factory - Crée des familles d'objets dépendants associés
  4. Builder - Construit des objets complexes en utilisant une approche étape par étape

Discutons maintenant de chacun de ces modèles en détail.

3. Modèle de conception Singleton

Le Singleton Design Pattern vise à contrôler l'initialisation des objets d'une classe particulière en s'assurant qu'une seule instance de l'objet existe dans la machine virtuelle Java.

Une classe Singleton fournit également un point d'accès global unique à l'objet afin que chaque appel ultérieur au point d'accès ne renvoie que cet objet particulier.

3.1. Exemple de modèle de singleton

Bien que le modèle Singleton ait été introduit par GoF, l'implémentation d'origine est connue pour être problématique dans les scénarios multithread.

Donc, ici, nous allons suivre une approche plus optimale qui utilise une classe interne statique:

public class Singleton { private Singleton() {} private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }

Ici, nous avons créé une classe interne statique qui contient l'instance de la classe Singleton . Il crée l'instance uniquement lorsque quelqu'un appelle la méthode getInstance () et non lorsque la classe externe est chargée.

Il s'agit d'une approche largement utilisée pour une classe Singleton car elle ne nécessite pas de synchronisation, est thread-safe, applique une initialisation paresseuse et a comparativement moins de passe-partout.

Notez également que le constructeur a le modificateur d'accès privé . C'est une exigence pour créer un Singleton car un constructeur public signifierait que n'importe qui pourrait y accéder et commencer à créer de nouvelles instances.

N'oubliez pas qu'il ne s'agit pas de l'implémentation originale du GoF. Pour la version originale, veuillez visiter cet article de Baeldung sur les singletons en Java.

3.2. Quand utiliser le modèle de conception Singleton

  • Pour les ressources coûteuses à créer (comme les objets de connexion à la base de données)
  • Il est recommandé de conserver tous les enregistreurs sous forme de singletons, ce qui augmente les performances
  • Classes donnant accès aux paramètres de configuration de l'application
  • Classes contenant des ressources accessibles en mode partagé

4. Modèle de conception de méthode d'usine

Le modèle de conception d'usine ou modèle de conception de méthode d'usine est l'un des modèles de conception les plus utilisés en Java.

Selon le GoF, ce modèle «définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe instancier. La méthode Factory permet à une classe de différer l'instanciation aux sous-classes ».

Ce modèle délègue la responsabilité d'initialiser une classe du client à une classe d'usine particulière en créant un type de constructeur virtuel.

Pour y parvenir, nous nous appuyons sur une usine qui nous fournit les objets, cachant les détails d'implémentation réels. Les objets créés sont accessibles à l'aide d'une interface commune.

4.1. Exemple de modèle de conception de méthode d'usine

Dans cet exemple, nous allons créer une interface Polygon qui sera implémentée par plusieurs classes concrètes. Une PolygonFactory sera utilisée pour récupérer les objets de cette famille:

Créons d'abord l' interface Polygon :

public interface Polygon { String getType(); }

Ensuite, nous allons créer quelques implémentations comme Square , Triangle, etc. qui implémentent cette interface et retournent un objet de type Polygon .

Nous pouvons maintenant créer une usine qui prend le nombre de côtés comme argument et renvoie l'implémentation appropriée de cette interface:

public class PolygonFactory { public Polygon getPolygon(int numberOfSides) { if(numberOfSides == 3) { return new Triangle(); } if(numberOfSides == 4) { return new Square(); } if(numberOfSides == 5) { return new Pentagon(); } if(numberOfSides == 7) { return new Heptagon(); } else if(numberOfSides == 8) { return new Octagon(); } return null; } }

Remarquez comment le client peut s'appuyer sur cette fabrique pour nous donner un polygone approprié , sans avoir à initialiser l'objet directement.

4.2. Quand utiliser le modèle de conception de méthode d'usine

  • Lorsque l'implémentation d'une interface ou d'une classe abstraite est censée changer fréquemment
  • Lorsque la mise en œuvre actuelle ne peut pas s'adapter confortablement à un nouveau changement
  • Lorsque le processus d'initialisation est relativement simple et que le constructeur ne nécessite qu'une poignée de paramètres

5. Abstract Factory Design Pattern

In the previous section, we saw how the Factory Method design pattern could be used to create objects related to a single family.

By contrast, the Abstract Factory Design Pattern is used to create families of related or dependent objects. It's also sometimes called a factory of factories.

For a detailed explanation, check out our Abstract Factory tutorial.

6. Builder Design Pattern

The Builder Design Pattern is another creational pattern designed to deal with the construction of comparatively complex objects.

When the complexity of creating object increases, the Builder pattern can separate out the instantiation process by using another object (a builder) to construct the object.

This builder can then be used to create many other similar representations using a simple step-by-step approach.

6.1. Builder Pattern Example

The original Builder Design Pattern introduced by GoF focuses on abstraction and is very good when dealing with complex objects, however, the design is a little complicated.

Joshua Bloch, in his book Effective Java, introduced an improved version of the builder pattern which is clean, highly readable (because it makes use of fluent design) and easy to use from client's perspective. In this example, we'll discuss that version.

This example has only one class, BankAccount which contains a builder as a static inner class:

public class BankAccount { private String name; private String accountNumber; private String email; private boolean newsletter; // constructors/getters public static class BankAccountBuilder { // builder code } } 

Note that all the access modifiers on the fields are declared private since we don't want outer objects to access them directly.

The constructor is also private so that only the Builder assigned to this class can access it. All of the properties set in the constructor are extracted from the builder object which we supply as an argument.

We've defined BankAccountBuilder in a static inner class:

public static class BankAccountBuilder { private String name; private String accountNumber; private String email; private boolean newsletter; public BankAccountBuilder(String name, String accountNumber) { this.name = name; this.accountNumber = accountNumber; } public BankAccountBuilder withEmail(String email) { this.email = email; return this; } public BankAccountBuilder wantNewsletter(boolean newsletter) { this.newsletter = newsletter; return this; } public BankAccount build() { return new BankAccount(this); } } 

Notice we've declared the same set of fields that the outer class contains. Any mandatory fields are required as arguments to the inner class's constructor while the remaining optional fields can be specified using the setter methods.

This implementation also supports the fluent design approach by having the setter methods return the builder object.

Finally, the build method calls the private constructor of the outer class and passes itself as the argument. The returned BankAccount will be instantiated with the parameters set by the BankAccountBuilder.

Let's see a quick example of the builder pattern in action:

BankAccount newAccount = new BankAccount .BankAccountBuilder("Jon", "22738022275") .withEmail("[email protected]") .wantNewsletter(true) .build();

6.2. When to Use Builder Pattern

  1. When the process involved in creating an object is extremely complex, with lots of mandatory and optional parameters
  2. When an increase in the number of constructor parameters leads to a large list of constructors
  3. When client expects different representations for the object that's constructed

7. Conclusion

Dans cet article, nous avons découvert les modèles de conception créative en Java. Nous avons également discuté de leurs quatre types différents, à savoir, Singleton, Factory Method, Abstract Factory et Builder Pattern, leurs avantages, leurs exemples et quand les utiliser.

Comme toujours, les extraits de code complets sont disponibles à l'adresse over sur GitHub.