Design patterns
Usine
Recherche…
Remarques
Fournir une interface pour créer des familles d'objets associés ou dépendants sans spécifier leurs classes concrètes.
- GOF 1994
Usine simple (Java)
Une fabrique diminue le couplage entre le code qui doit créer des objets à partir du code de création d'objet. La création d'objet n'est pas faite explicitement en appelant un constructeur de classe, mais en appelant une fonction qui crée l'objet au nom de l'appelant. Un exemple Java simple est le suivant:
interface Car {
}
public class CarFactory{
static public Car create(String s) {
switch (s) {
default:
case "us":
case "american": return new Chrysler();
case "de":
case "german": return new Mercedes();
case "jp":
case "japanese": return new Mazda();
}
}
}
class Chrysler implements Car {
public String toString() { return "Chrysler"; }
}
class Mazda implements Car {
public String toString() { return "Mazda"; }
}
class Mercedes implements Car {
public String toString() { return "Mercedes"; }
}
public class CarEx {
public static void main(String args[]) {
Car car = CarFactory.create("us");
System.out.println(car);
}
}
Dans cet exemple, l'utilisateur donne un aperçu de ce dont il a besoin et l'usine est libre de construire quelque chose de approprié. C'est une inversion de dépendance : l'implémenteur de Car
concept est libre de renvoyer une Car
concrète appropriée demandée par l'utilisateur qui à son tour ne connaît pas les détails de l'objet concret construit.
Ceci est un exemple simple de fonctionnement de l'usine, bien sûr, dans cet exemple, il est toujours possible d'instancier des classes concrètes; mais on peut l'empêcher en cachant des classes de béton dans un paquet, de telle sorte que l'utilisateur est obligé d'utiliser l'usine.
.Net Fiddle pour l'exemple ci-dessus.
Fabrique abstraite (C ++)
Le motif d' usine abstrait fournit un moyen d'obtenir une collection cohérente d'objets à travers un ensemble de fonctions de fabriques. Comme pour tout modèle, le couplage est réduit en faisant abstraction de la manière dont un ensemble d'objets est créé, de sorte que le code utilisateur ignore les nombreux détails des objets dont il a besoin.
L'exemple C ++ suivant illustre comment obtenir différents types d'objets de la même famille d'interface graphique (hypothétique):
#include <iostream>
/* Abstract definitions */
class GUIComponent {
public:
virtual ~GUIComponent() = default;
virtual void draw() const = 0;
};
class Frame : public GUIComponent {};
class Button : public GUIComponent {};
class Label : public GUIComponent {};
class GUIFactory {
public:
virtual ~GUIFactory() = default;
virtual std::unique_ptr<Frame> createFrame() = 0;
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<Label> createLabel() = 0;
static std::unique_ptr<GUIFactory> create(const std::string& type);
};
/* Windows support */
class WindowsFactory : public GUIFactory {
private:
class WindowsFrame : public Frame {
public:
void draw() const override { std::cout << "I'm a Windows-like frame" << std::endl; }
};
class WindowsButton : public Button {
public:
void draw() const override { std::cout << "I'm a Windows-like button" << std::endl; }
};
class WindowsLabel : public Label {
public:
void draw() const override { std::cout << "I'm a Windows-like label" << std::endl; }
};
public:
std::unique_ptr<Frame> createFrame() override { return std::make_unique<WindowsFrame>(); }
std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
std::unique_ptr<Label> createLabel() override { return std::make_unique<WindowsLabel>(); }
};
/* Linux support */
class LinuxFactory : public GUIFactory {
private:
class LinuxFrame : public Frame {
public:
void draw() const override { std::cout << "I'm a Linux-like frame" << std::endl; }
};
class LinuxButton : public Button {
public:
void draw() const override { std::cout << "I'm a Linux-like button" << std::endl; }
};
class LinuxLabel : public Label {
public:
void draw() const override { std::cout << "I'm a Linux-like label" << std::endl; }
};
public:
std::unique_ptr<Frame> createFrame() override { return std::make_unique<LinuxFrame>(); }
std::unique_ptr<Button> createButton() override { return std::make_unique<LinuxButton>(); }
std::unique_ptr<Label> createLabel() override { return std::make_unique<LinuxLabel>(); }
};
std::unique_ptr<GUIFactory> GUIFactory::create(const string& type) {
if (type == "windows") return std::make_unique<WindowsFactory>();
return std::make_unique<LinuxFactory>();
}
/* User code */
void buildInterface(GUIFactory& factory) {
auto frame = factory.createFrame();
auto button = factory.createButton();
auto label = factory.createLabel();
frame->draw();
button->draw();
label->draw();
}
int main(int argc, char *argv[]) {
if (argc < 2) return 1;
auto guiFactory = GUIFactory::create(argv[1]);
buildInterface(*guiFactory);
}
Si l'exécutable généré s'appelle abstractfactory
alors la sortie peut donner:
$ ./abstractfactory windows
I'm a Windows-like frame
I'm a Windows-like button
I'm a Windows-like label
$ ./abstractfactory linux
I'm a Linux-like frame
I'm a Linux-like button
I'm a Linux-like label
Exemple simple de Factory qui utilise un IoC (C #)
Les usines peuvent également être utilisées avec les bibliothèques Inversion of Control (IoC).
- Le cas d'utilisation typique d'une telle fabrique est le moment où nous voulons créer un objet en fonction de paramètres inconnus avant l'exécution (comme l'utilisateur actuel).
- Dans ces cas, il peut être parfois difficile (voire impossible) de configurer la bibliothèque IoC seule pour gérer ce type d'informations contextuelles à l'exécution, afin que nous puissions l'envelopper dans une usine.
Exemple
- Supposons que nous ayons une classe
User
, dont les caractéristiques (ID, niveau d'autorisation de sécurité, etc.) sont inconnues avant l'exécution (l'utilisateur actuel pouvant être toute personne utilisant l'application). - Nous devons prendre l'utilisateur actuel et obtenir un
ISecurityToken
pour eux, qui peut ensuite être utilisé pour vérifier si l'utilisateur est autorisé à effectuer certaines actions ou non. - L'implémentation d'ISecurityToken variera en fonction du niveau de l'utilisateur - en d'autres termes, ISecurityToken utilise un polymorphisme .
Dans ce cas, nous avons deux implémentations, qui utilisent également des interfaces de marqueur pour faciliter leur identification dans la bibliothèque IoC; la bibliothèque IoC dans ce cas est juste composée et identifiée par l'abstraction IContainer
.
Notez également que de nombreuses usines IoC modernes ont des capacités ou des plug-ins natifs qui permettent la création automatique de fabriques, tout en évitant les interfaces de marqueur, comme indiqué ci-dessous; Cependant, comme tous ne le font pas, cet exemple répond à un concept de fonctionnalité commun simple et minimal.
//describes the ability to allow or deny an action based on PerformAction.SecurityLevel
public interface ISecurityToken
{
public bool IsAllowedTo(PerformAction action);
}
//Marker interface for Basic permissions
public interface IBasicToken:ISecurityToken{};
//Marker interface for super permissions
public interface ISuperToken:ISecurityToken{};
//since IBasictoken inherits ISecurityToken, BasicToken can be treated as an ISecurityToken
public class BasicToken:IBasicToken
{
public bool IsAllowedTo(PerformAction action)
{
//Basic users can only perform basic actions
if(action.SecurityLevel!=SecurityLevel.Basic) return false;
return true;
}
}
public class SuperToken:ISuperToken
{
public bool IsAllowedTo(PerformAction action)
{
//Super users can perform all actions
return true;
}
}
Ensuite, nous allons créer une usine SecurityToken
, qui prendra comme dépendance notre IContainer
public class SecurityTokenFactory
{
readonly IContainer _container;
public SecurityTokenFactory(IContainer container)
{
if(container==null) throw new ArgumentNullException("container");
}
public ISecurityToken GetToken(User user)
{
if (user==null) throw new ArgumentNullException("user);
//depending on the user security level, we return a different type; however all types implement ISecurityToken so the factory can produce them.
switch user.SecurityLevel
{
case Basic:
return _container.GetInstance<BasicSecurityToken>();
case SuperUser:
return _container.GetInstance<SuperUserToken>();
}
}
}
Une fois que nous les avons enregistrés avec IContainer
:
IContainer.For<SecurityTokenFactory>().Use<SecurityTokenFactory>().Singleton(); //we only need a single instance per app
IContainer.For<IBasicToken>().Use<BasicToken>().PerRequest(); //we need an instance per-request
IContainer.For<ISuperToken>().Use<SuperToken>().PerRequest();//we need an instance per-request
le code consommateur peut l'utiliser pour obtenir le bon jeton à l'exécution:
readonly SecurityTokenFactory _tokenFactory;
...
...
public void LogIn(User user)
{
var token = _tokenFactory.GetToken(user);
user.SetSecurityToken(token);
}
De cette manière, nous bénéficions de l'encapsulation fournie par l'usine et de la gestion du cycle de vie fournie par la bibliothèque IoC.
Une fabrique abstraite
Le motif de conception suivant est classé comme motif de création.
Une fabrique abstraite est utilisée pour fournir une interface pour créer des familles d'objets associés, sans spécifier de classes concrètes et peut être utilisée pour masquer des classes spécifiques à une plate-forme.
interface Tool {
void use();
}
interface ToolFactory {
Tool create();
}
class GardenTool implements Tool {
@Override
public void use() {
// Do something...
}
}
class GardenToolFactory implements ToolFactory {
@Override
public Tool create() {
// Maybe additional logic to setup...
return new GardenTool();
}
}
class FarmTool implements Tool {
@Override
public void use() {
// Do something...
}
}
class FarmToolFactory implements ToolFactory {
@Override
public Tool create() {
// Maybe additional logic to setup...
return new FarmTool();
}
}
Ensuite, on utilisera un fournisseur / producteur qui transmettra des informations lui permettant de restituer le type correct d'implémentation d'usine:
public final class FactorySupplier {
// The supported types it can give you...
public enum Type {
FARM, GARDEN
};
private FactorySupplier() throws IllegalAccessException {
throw new IllegalAccessException("Cannot be instantiated");
}
public static ToolFactory getFactory(Type type) {
ToolFactory factory = null;
switch (type) {
case FARM:
factory = new FarmToolFactory();
break;
case GARDEN:
factory = new GardenToolFactory();
break;
} // Could potentially add a default case to handle someone passing in null
return factory;
}
}
Exemple d'usine en implémentant la méthode Factory (Java)
Intention:
Définissez une interface pour créer un objet, mais laissez les sous-classes décider quelle classe instancier. La méthode d'usine permet à une classe de différer l'instanciation aux sous-classes.
Diagramme UML:
Produit: définit une interface des objets créés par la méthode Factory.
ConcreteProduct: Implémente l'interface produit
Créateur: déclare la méthode Factory
ConcreateCreator: Implémente la méthode Factory pour retourner une instance d'un ConcreteProduct
Énoncé du problème: Créez une fabrique de jeux en utilisant les méthodes d'usine, qui définissent l'interface de jeu.
Extrait de code:
import java.util.HashMap;
/* Product interface as per UML diagram */
interface Game{
/* createGame is a complex method, which executes a sequence of game steps */
public void createGame();
}
/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
public Chess(){
createGame();
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Chess game");
System.out.println("Opponents:2");
System.out.println("Define 64 blocks");
System.out.println("Place 16 pieces for White opponent");
System.out.println("Place 16 pieces for Black opponent");
System.out.println("Start Chess game");
System.out.println("---------------------------------------");
}
}
class Checkers implements Game{
public Checkers(){
createGame();
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Checkers game");
System.out.println("Opponents:2 or 3 or 4 or 6");
System.out.println("For each opponent, place 10 coins");
System.out.println("Start Checkers game");
System.out.println("---------------------------------------");
}
}
class Ludo implements Game{
public Ludo(){
createGame();
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Ludo game");
System.out.println("Opponents:2 or 3 or 4");
System.out.println("For each opponent, place 4 coins");
System.out.println("Create two dices with numbers from 1-6");
System.out.println("Start Ludo game");
System.out.println("---------------------------------------");
}
}
/* Creator interface as per UML diagram */
interface IGameFactory {
public Game getGame(String gameName);
}
/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {
HashMap<String,Game> games = new HashMap<String,Game>();
/*
Since Game Creation is complex process, we don't want to create game using new operator every time.
Instead we create Game only once and store it in Factory. When client request a specific game,
Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
*/
public GameFactory(){
games.put(Chess.class.getName(),new Chess());
games.put(Checkers.class.getName(),new Checkers());
games.put(Ludo.class.getName(),new Ludo());
}
public Game getGame(String gameName){
return games.get(gameName);
}
}
public class NonStaticFactoryDemo{
public static void main(String args[]){
if ( args.length < 1){
System.out.println("Usage: java FactoryDemo gameName");
return;
}
GameFactory factory = new GameFactory();
Game game = factory.getGame(args[0]);
System.out.println("Game="+game.getClass().getName());
}
}
sortie:
java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
---------------------------------------
Create Checkers game
Opponents:2 or 3 or 4 or 6
For each opponent, place 10 coins
Start Checkers game
---------------------------------------
---------------------------------------
Create Ludo game
Opponents:2 or 3 or 4
For each opponent, place 4 coins
Create two dices with numbers from 1-6
Start Ludo game
---------------------------------------
Game=Chess
Cet exemple montre une classe Factory
en implémentant une FactoryMethod
.
Game
est l'interface pour tous les types de jeux. Il définit la méthode complexe:createGame()
Chess, Ludo, Checkers
sont différentes variantes de jeux, qui implémententcreateGame()
public Game getGame(String gameName)
estFactoryMethod
dans la classeIGameFactory
GameFactory
pré-crée différents types de jeux dans le constructeur. Il implémente la méthode de fabricationIGameFactory
.game Name est passé en argument de ligne de commande à
NotStaticFactoryDemo
getGame
dansGameFactory
accepte un nom de jeu et renvoie l'objet deGame
correspondant.
Quand utiliser:
- Factory : lorsque vous ne souhaitez pas exposer la logique d'instanciation d'objet au client / à l'appelant
- Fabrique abstraite : lorsque vous souhaitez fournir une interface aux familles d'objets associés ou dépendants sans spécifier leurs classes concrètes
- Méthode d'usine: Définir une interface pour créer un objet, mais laisser les sous-classes décider quelle classe instancier
Comparaison avec d'autres modèles créatifs:
Conception démarrée à l'aide de la méthode d'usine (moins compliquée, plus personnalisable, prolifération de sous-classes) et évolution vers la fabrique abstraite, le prototype ou le générateur (plus flexible, plus complexe) que le concepteur découvre
Les classes Factory abstraites sont souvent implémentées avec les méthodes Factory , mais elles peuvent aussi être implémentées avec Prototype
Références pour d'autres lectures: Modèles de conception de fabrication
Usine de poids mouche (C #)
En termes simples:
Une usine Flyweight qui, pour une clé donnée, déjà connue, donnera toujours le même objet que la réponse. Pour les nouvelles clés créera l'instance et la renverra.
En utilisant l'usine:
ISomeFactory<string, object> factory = new FlyweightFactory<string, object>();
var result1 = factory.GetSomeItem("string 1");
var result2 = factory.GetSomeItem("string 2");
var result3 = factory.GetSomeItem("string 1");
//Objects from different keys
bool shouldBeFalse = result1.Equals(result2);
//Objects from same key
bool shouldBeTrue = result1.Equals(result3);
La mise en oeuvre:
public interface ISomeFactory<TKey,TResult> where TResult : new()
{
TResult GetSomeItem(TKey key);
}
public class FlyweightFactory<TKey, TResult> : ISomeFactory<TKey, TResult> where TResult : new()
{
public TResult GetSomeItem(TKey key)
{
TResult result;
if(!Mapping.TryGetValue(key, out result))
{
result = new TResult();
Mapping.Add(key, result);
}
return result;
}
public Dictionary<TKey, TResult> Mapping { get; set; } = new Dictionary<TKey, TResult>();
}
Notes supplémentaires
Je recommande d'ajouter à cette solution l'utilisation d'un IoC Container
(comme expliqué dans un exemple différent ici) au lieu de créer vos propres nouvelles instances. On peut le faire en ajoutant un nouvel enregistrement pour le TResult
au conteneur, puis en le résolvant (au lieu du dictionary
dans l'exemple).
Méthode d'usine
Le modèle de méthode Factory est un modèle de création qui élimine la logique d'instanciation d'un objet afin de découpler le code client.
Lorsqu'une méthode de fabrique appartient à une classe qui est une implémentation d'un autre modèle de fabrique tel qu'une fabrique abstraite, il est généralement plus approprié de référencer le modèle implémenté par cette classe plutôt que le modèle de méthode Factory.
Le modèle de méthode Factory est plus communément utilisé pour décrire une méthode de fabrique appartenant à une classe qui n’est pas principalement une fabrique.
Par exemple, il peut être avantageux de placer une méthode de fabrique sur un objet qui représente un concept de domaine si cet objet encapsule un état qui simplifierait le processus de création d'un autre objet. Une méthode d'usine peut également conduire à une conception plus alignée sur la langue ubiquitaire d'un contexte spécifique.
Voici un exemple de code:
//Without a factory method
Comment comment = new Comment(authorId, postId, "This is a comment");
//With a factory method
Comment comment = post.comment(authorId, "This is a comment");