À ce stade, vous avez probablement déjà rencontré des modèles de conception. Par exemple, singleton .

Rappelons-nous ce que sont les modèles, pourquoi ils sont nécessaires et quels sont les modèles de création (singleton en est un exemple). Nous étudierons également un nouveau modèle : la méthode de l'usine.

Dans le développement de logiciels, un modèle de conception est une construction architecturale reproductible qui représente une solution à un problème de conception dans un contexte récurrent.

En règle générale, un modèle n'est pas une solution finale qui peut être directement convertie en code. C'est juste un modèle de solution à un problème qui peut être utilisé dans diverses situations.

Les modèles de création sont des modèles de conception qui traitent du processus de création d'objets. Ils permettent de créer un système indépendant de la méthode utilisée pour créer, composer et présenter des objets.

Une méthode de fabrique est un modèle de conception créationnel qui définit une interface commune pour créer des objets dans une classe parente, donnant à ses descendants la possibilité de créer ces objets. Au moment de la création, les descendants peuvent déterminer quelle classe créer.

Quel problème le modèle résout-il ?

Imaginez que vous décidiez de créer un programme de livraison. Dans un premier temps, vous engagerez des coursiers avec des voitures et utiliserez unVoitureobjet pour représenter un véhicule de livraison dans le programme. Les coursiers livrent les colis du point A au point B et ainsi de suite. Très facile.

Le programme gagne en popularité. Votre entreprise se développe et vous souhaitez vous développer sur de nouveaux marchés. Par exemple, vous pouvez également commencer à livrer de la nourriture et à expédier du fret. Dans ce cas, la nourriture peut être livrée par des coursiers à pied, en scooter et à vélo, mais des camions sont nécessaires pour le fret.

Maintenant, vous devez garder une trace de plusieurs choses (quand, à qui, quoi et combien seront livrés), y compris combien chaque coursier peut transporter. Les nouveaux modes de transport ont des vitesses et des capacités différentes. Ensuite, vous remarquez que la plupart des entités de votre programme sont fortement liées auVoitureclasse. Vous vous rendez compte que pour que votre programme fonctionne avec d'autres méthodes de livraison, vous devrez réécrire la base de code existante et recommencer chaque fois que vous ajouterez un nouveau véhicule.

Le résultat est un code horrible rempli d'instructions conditionnelles qui effectuent différentes actions en fonction du type de transport.

La solution

Le modèle de méthode de fabrique suggère de créer des objets en appelant une méthode de fabrique spéciale plutôt que d'utiliser directement l' opérateur new . Les sous-classes de la classe qui a la méthode d'usine peuvent modifier les objets créés des véhicules spécifiques. À première vue, cela peut sembler inutile : nous avons simplement déplacé l'appel du constructeur d'un endroit du programme à un autre. Mais maintenant, vous pouvez remplacer la méthode d'usine dans une sous-classe pour modifier le type de transport en cours de création.

Regardons le diagramme de classes pour cette approche :

Pour que ce système fonctionne, tous les objets retournés doivent avoir une interface commune. Les sous-classes pourront produire des objets de différentes classes qui implémentent cette interface.

Par exemple, les classes Truck et Car implémentent l' interface CourierTransport avec une méthode de livraison . Chacune de ces classes implémente la méthode d'une manière différente : les camions livrent du fret, tandis que les voitures livrent de la nourriture, des colis, etc. La méthode factory de la classe TruckCreator renvoie un objet camion et la classe CarCreator renvoie un objet voiture.

Pour le client de la méthode factory, il n'y a pas de différence entre ces objets, puisqu'il les traitera comme une sorte de CourierTransport abstrait . Le client se souciera profondément que l'objet ait une méthode de livraison, mais le fonctionnement exact de cette méthode n'est pas important.

Implémentation en Java :

public interface CourierTransport {
	void deliver();
}
public class Car implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The package is being delivered by car");
	}
}
public class Truck implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The freight is being delivered by truck");
	}
}
public abstract class CourierTransportCreator {
	public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Car();
	}
}
public class TruckCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Truck();
	}
}

public class Delivery {
	private String address;
	private CourierTransport courierTransport;

	public void Delivery() {
	}

	public Delivery(String address, CourierTransport courierTransport) {
    	this.address = address;
    	this.courierTransport = courierTransport;
	}

	public CourierTransport getCourierTransport() {
    		return courierTransport;
	}

	public void setCourierTransport(CourierTransport courierTransport) {
    		this.courierTransport = courierTransport;
	}

	public String getAddress() {
    		return address;
	}

	public void setAddress(String address) {
    		this.address = address;
	}
}
public static void main(String[] args) {
    	// Accept a new type of order from the database (pseudocode)
    	String type = database.getTypeOfDeliver();

    	Delivery delivery = new Delivery();

    	// Set the transport for delivery
        delivery.setCourierTransport(getCourierTransportByType(type));

    	// Make the delivery
        delivery.getCourierTransport().deliver();

	}

	public static CourierTransport getCourierTransportByType(String type) {
    	switch (type) {
        	case "CarDelivery":
            	return new CarCreator().createTransport();
        	case "TruckDelivery":
            	return new TruckCreator().createTransport();
        	default:
            	throw new RuntimeException();
	    }
	}

Si nous voulons créer un nouvel objet de livraison, le programme crée automatiquement un objet de transport approprié.

Quand doit-on appliquer ce modèle ?

1. Lorsque vous ne connaissez pas à l'avance les types et les dépendances des objets avec lesquels votre code doit fonctionner.

La méthode d'usine sépare le code de production des moyens de transport du code qui utilise le transport. Par conséquent, le code de création d'objets peut être étendu sans toucher au reste du code.

Par exemple, pour ajouter la prise en charge d'un nouveau type de transport, vous devez créer une nouvelle sous-classe et y définir une méthode de fabrique qui renvoie une instance du nouveau transport.

2. Lorsque vous souhaitez économiser des ressources système en réutilisant des objets existants au lieu d'en créer de nouveaux.

Ce problème se produit généralement lorsque vous travaillez avec des objets gourmands en ressources, tels que des connexions de base de données, des systèmes de fichiers, etc.

Pensez aux étapes que vous devez suivre pour réutiliser des objets existants :

  1. Tout d'abord, vous devez créer un référentiel partagé pour stocker tous les objets que vous créez.

  2. Lorsque vous demandez un nouvel objet, vous devez regarder dans le référentiel et vérifier s'il contient un objet disponible.

  3. Renvoie l'objet au code client.

  4. Mais s'il n'y a pas d'objets disponibles, créez-en un nouveau et ajoutez-le au référentiel.

Tout ce code doit être mis quelque part qui n'encombrera pas le code client. L'endroit le plus pratique serait le constructeur, car nous n'avons besoin de toutes ces vérifications que lors de la création d'objets. Hélas, un constructeur crée toujours un nouvel objet — il ne peut pas retourner un objet existant.

Cela signifie qu'une autre méthode est nécessaire pour renvoyer à la fois des objets existants et nouveaux. Ce sera la méthode d'usine.

3. Lorsque vous souhaitez autoriser les utilisateurs à étendre des parties de votre framework ou bibliothèque.

Les utilisateurs peuvent étendre vos classes de framework par héritage. Mais comment faire en sorte que le framework crée des objets de ces nouvelles classes plutôt que les standards ?

La solution consiste à laisser les utilisateurs étendre non seulement les composants, mais également les classes qui créent ces composants. Et pour cela, les classes créatrices doivent avoir des méthodes de création spécifiques qui peuvent être définies.

Avantages

  • Dissocie une classe de classes de transport spécifiques.
  • Conserve le code pour créer des formes de transport en un seul endroit, ce qui facilite la maintenance du code.
  • Simplifie l'ajout de nouveaux modes de transport au programme.
  • Met en œuvre le principe ouvert-fermé.

Désavantages

Peut conduire à de grandes hiérarchies de classes parallèles, puisque chaque classe de produits doit avoir sa propre sous-classe de créateur.

Résumons

Vous avez découvert le modèle de méthode d'usine et vu une implémentation possible. Ce modèle est souvent utilisé dans diverses bibliothèques qui fournissent des objets pour créer d'autres objets.

Utilisez le modèle de méthode d'usine lorsque vous souhaitez ajouter facilement de nouveaux objets de sous-classes de classes existantes afin d'interagir avec votre logique métier principale et de ne pas gonfler votre code en raison de différents contextes.