Constructeurs Java vs méthodes d’usine statiques

Constructeurs Java vs méthodes d'usine statique

1. Vue d'ensemble

Les constructeurs Java sont le mécanisme par défaut pour obtenir des instances de classe entièrement initialisées. Après tout, ils fournissent toute l’infrastructure requise pour l’injection de dépendances, manuellement ou automatiquement.

Néanmoins, dans quelques cas d’utilisation spécifiques, il est préférable de recourir à des méthodes d’usine statiques pour obtenir le même résultat.

Dans ce tutoriel, nous mettrons en évidence lespros and cons of using static factory methods vs plain old Java constructors.

2. Les avantages de Méthodes d'usine statiques sur les constructeurs

Dans un langage orienté objet tel que Java, qu'est-ce qui ne va pas avec les constructeurs? Dans l'ensemble, rien. Même ainsi, le fameuxJoshua Block’s Effective Java Item 1 déclare clairement:

"Considérez les méthodes de fabrique statiques au lieu des constructeurs"

Bien que ce ne soit pas une solution miracle, voici les raisons les plus convaincantes qui soutiennent cette approche:

  1. Constructors don’t have meaningful names, donc ils sont toujours limités à la convention de dénomination standard imposée par le langage. Static factory methods can have meaningful names, transmettant donc explicitement ce qu'ils font

  2. Static factory methods can return the same type that implements the method(s), a subtype, and also primitives, ils offrent donc une gamme plus flexible de types de retour

  3. Static factory methods can encapsulate all the logic required for pre-constructing fully initialized instances, afin qu'ils puissent être utilisés pour déplacer cette logique supplémentaire hors des constructeurs. Cela empêche les constructeurs deperforming further tasks, others than just initializing fields

  4. Static factory methods can be controlled-instanced methods, leSingleton pattern étant l'exemple le plus flagrant de cette fonctionnalité

3. Méthodes de fabrique statique dans le JDK

Il existe de nombreux exemples de méthodes d'usine statiques dans le JDK qui illustrent nombre des avantages décrits ci-dessus. Explorons-en quelques-uns.

3.1. La classeString

En raison desString interning bien connus, il est très peu probable que nous utilisions le constructeur de classeString pour créer un nouvel objetString. Même dans ce cas, c'est parfaitement légal:

String value = new String("example");

Dans ce cas, le constructeur créera un nouvel objetString, qui est le comportement attendu.

Alternativement, si nous voulonscreate a new String object using a static factory method, nous pouvons utiliser certaines des implémentations suivantes de la méthodevalueOf():

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

Il existe plusieurs implémentations surchargées devalueOf(). Chacun renverra un nouvel objetString, en fonction du type d'argument passé à la méthode (par exemple int,long,boolean,char, et ainsi de suite).

Le nom exprime assez clairement ce que fait la méthode. Il s'en tient également à une norme bien établie de l'écosystème Java pour nommer les méthodes fabriques statiques.

3.2. La classeOptional

Un autre exemple intéressant de méthodes de fabrique statique dans le JDK est la classeOptional. Cette classeimplements a few factory methods with pretty meaningful names, y comprisempty(),of() etofNullable():

Optional value1 = Optional.empty();
Optional value2 = Optional.of("example");
Optional value3 = Optional.ofNullable(null);

3.3. La classeCollections

Très probablementthe most representative example of static factory methods in the JDK is the Collections class. Il s'agit d'une classe non instanciable qui implémente uniquement des méthodes statiques.

La plupart d'entre elles sont des méthodes d'usine qui renvoient également des collections, après avoir appliqué à la collection fournie un type d'algorithme.

Voici quelques exemples typiques des méthodes d'usine de la classe:

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List unmodifiableList = Collections.unmodifiableList(originalList);
Map unmodifiableMap = Collections.unmodifiableMap(originalMap);

Le nombre de méthodes de fabrique statiques dans le JDK est très important, nous allons donc garder la liste des exemples courte par souci de concision.

Néanmoins, les exemples ci-dessus devraient nous donner une idée précise de l’utilité des méthodes de fabrique statique en Java.

4. Méthodes d'usine statique personnalisées

Bien sûr,we can implement our own static factory methods. Mais quand cela vaut-il vraiment la peine de le faire, au lieu de créer des instances de classe via des constructeurs simples?

Voyons un exemple simple.

Considérons cette classe naïveUser:

public class User {

    private final String name;
    private final String email;
    private final String country;

    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }

    // standard getters / toString
}

Dans ce cas, aucun avertissement visible n’indique qu’une méthode de fabrique statique pourrait être meilleure que le constructeur standard.

Que faire si nous voulons que toutes les instancesUser obtiennent une valeur par défaut pour le champcountry?

Si nous initialisons le champ avec une valeur par défaut, nous devrons également refactoriser le constructeur, rendant ainsi la conception plus rigide.

Nous pouvons utiliser une méthode de fabrique statique à la place:

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

Voici comment obtenir une instanceUser avec une valeur par défaut attribuée au champcountry:

User user = User.createWithDefaultCountry("John", "[email protected]");

5. Déplacer la logique hors des constructeurs

Notre classeUser pourrait rapidement se transformer en une conception défectueuse si nous décidons d'implémenter des fonctionnalités qui nécessiteraient d'ajouter une logique supplémentaire au constructeur (les sonnettes d'alarme devraient sonner à ce moment-là).

Supposons que nous voulions fournir à la classe la possibilité de consigner l'heure à laquelle chaque objetUser est créé.

If we just put this logic into the constructor, we’d be breaking the Single Responsibility Principle. Nous nous retrouverions avec un constructeur monolithique qui fait beaucoup plus que d’initialiser des champs.

Nous pouvons garder notre conception propre avec une méthode d'usine statique:

public class User {

    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;

    // standard constructors / getters

    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        setLoggerProperties();
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }

    private static void setLoggerProperties() {
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.INFO);
        handler.setFormatter(new SimpleFormatter());
        LOGGER.addHandler(handler);
    }
}

Voici comment nous créerons notre instanceUser améliorée:

User user
  = User.createWithLoggedInstantiationTime("John", "[email protected]", "Argentina");

6. Instanciation contrôlée par instance

Comme indiqué ci-dessus, nous pouvons encapsuler des morceaux de logique dans des méthodes de fabrique statiques avant de renvoyer des objetsUser entièrement initialisés. Et nous pouvons le faire sans polluer le constructeur avec la responsabilité d’exécuter de multiples tâches non liées.

Par exemple,suppose we want to make our User class a Singleton. We can achieve this by implementing an instance-controlled static factory method:

public class User {

    private static volatile User instance = null;

    // other fields / standard constructors / getters

    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

L'implémentation de la méthodegetSingletonInstance() estthread-safe, with a small performance penalty, due to the synchronized block.

Dans ce cas, nous avons utilisé l'initialisation différée pour démontrer la mise en œuvre d'une méthode de fabrique statique contrôlée par l'instance.

Il convient cependant de mentionner quethe best way to implement a Singleton is with a Java enum type, as it’s both serialization-safe and thread-safe. Pour plus de détails sur la façon d'implémenter des Singletons en utilisant différentes approches, veuillez vérifierthis article.

Comme prévu, l'obtention d'un objetUser avec cette méthode ressemble beaucoup aux exemples précédents:

User user = User.getSingletonInstance("John", "[email protected]", "Argentina");

7. Conclusion

Dans cet article, nous avons étudié quelques cas d'utilisation où les méthodes fabriques statiques peuvent constituer une meilleure alternative à l'utilisation de constructeurs Java simples.

De plus, ce modèle de refactoring est si étroitement lié à un flux de travail typique que la plupart des IDE le feront pour nous.

Bien sûr,Apache NetBeans,IntelliJ IDEA etEclipse effectueront le refactoring de manière légèrement différente, alors assurez-vous d'abord de vérifier la documentation de votre IDE.

Comme pour de nombreux autres modèles de refactoring, nous devons utiliser les méthodes d'usine statiques avec prudence, et uniquement lorsque cela vaut la peine de faire un compromis entre la production de conceptions plus flexibles et plus propres et le coût de la mise en œuvre de méthodes supplémentaires.

Comme d'habitude, tous les exemples de code présentés dans cet article sont disponiblesover on GitHub.