web-dev-qa-db-fra.com

Quand utiliseriez-vous le modèle de générateur?

Quels sont quelques communs, exemples du monde réel d'utilisation du modèle de générateur? Qu'est-ce qu'il t'achète? Pourquoi ne pas simplement utiliser un modèle d'usine?

501
Charles Graham

La principale différence entre un constructeur et un IMHO d'usine, est qu'un constructeur est utile lorsque vous devez faire beaucoup de choses pour construire un objet. Par exemple, imaginons un DOM. Vous devez créer de nombreux noeuds et attributs pour obtenir votre objet final. Une fabrique est utilisée lorsque la fabrique peut facilement créer l'objet entier dans un seul appel de méthode.

Un exemple d'utilisation d'un générateur est la construction d'un document XML. J'ai utilisé ce modèle lors de la création de fragments HTML. Par exemple, je pourrais avoir un générateur pour la construction d'un type spécifique de table et appliquer les méthodes suivantes (les paramètres ne sont pas affichés) :

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

Ce constructeur cracherait ensuite le code HTML pour moi. C’est beaucoup plus facile à lire que de parcourir une grande méthode procédurale.

Départ modèle de constructeur sur Wikipedia .

248
JoshBerke

Vous trouverez ci-dessous quelques raisons plaidant en faveur de l'utilisation du modèle et du code d'exemple en Java, mais il s'agit d'une implémentation du modèle de générateur couvert par le Gang of Four dans Modèles de conception . Les raisons pour lesquelles vous l'utiliseriez dans Java s'appliquent également à d'autres langages de programmation.

Comme Joshua Bloch l’indique dans Effective Java, 2nd Edition :

Le modèle de générateur est un bon choix lors de la conception de classes dont les constructeurs ou les usines statiques auraient plus qu'une poignée de paramètres.

Nous avons tous rencontré à un moment donné une classe avec une liste de constructeurs où chaque ajout ajoute un nouveau paramètre d'option:

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

C'est ce qu'on appelle le modèle de constructeur télescopique. Le problème de ce modèle est qu'une fois que les constructeurs ont 4 ou 5 paramètres de long, il devient difficile de se souvenir de l'ordre requis des paramètres ainsi que du constructeur que vous voudrez peut-être dans un situation.

Une alternative que vous avez pour le modèle de constructeur télescopique est le modèle JavaBean où vous appelez un constructeur avec les paramètres obligatoires, puis tous les paramètres optionnels après:

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

Le problème ici est que, parce que l'objet est créé sur plusieurs appels, il se peut qu'il soit dans un état incohérent tout au long de sa construction. Cela nécessite également beaucoup de effort pour assurer la sécurité du fil.

La meilleure solution consiste à utiliser le modèle de générateur.

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

    public Pizza build() {
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

Notez que Pizza est immuable et que les valeurs de paramètre sont toutes situées au même endroit . Comme les méthodes de définition du générateur renvoient l'objet générateur, elles peuvent être chaînées .

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

Il en résulte un code facile à écrire et très facile à lire et à comprendre. Dans cet exemple, la construction La méthode peut être modifiée pour vérifier les paramètres une fois qu'ils ont été copiés du générateur dans l'objet Pizza et lève une exception IllegalStateException si une valeur de paramètre non valide a été fournie. Ce modèle est flexible et il est facile d’y ajouter plus de paramètres à l’avenir. Ce n'est vraiment utile que si vous allez avoir plus de 4 ou 5 paramètres pour un constructeur. Cela dit, cela pourrait valoir la peine en premier lieu si vous pensez que vous ajouterez peut-être plus de paramètres dans le futur.

J'ai beaucoup emprunté à ce sujet dans le livre Effective Java, 2e édition de Joshua Bloch. Pour en savoir plus sur ce modèle et sur d’autres pratiques Java efficaces , je le recommande vivement.

975
Aaron

Considérons un restaurant. La création du "repas du jour" est un motif d'usine, car vous dites à la cuisine "procure-moi le repas du jour" et la cuisine (l'usine) décide quel objet générer, en fonction de critères cachés.

Le constructeur apparaît si vous commandez une pizza personnalisée. Dans ce cas, le serveur dit au chef d'établissement "J'ai besoin d'une pizza; ajoutez-y du fromage, des oignons et du bacon!" Ainsi, le générateur expose les attributs que doit avoir l'objet généré, mais cache comment les définir.

313
Tetha

La classe .NET StringBuilder est un excellent exemple de modèle de générateur. Il est principalement utilisé pour créer une chaîne en une série d'étapes. Le résultat final obtenu lors de l'exécution de ToString () est toujours une chaîne, mais la création de cette chaîne varie en fonction des fonctions utilisées dans la classe StringBuilder. En résumé, l’idée de base est de construire des objets complexes et de masquer les détails d’implémentation de la construction.

18
dssfsdf

Pour un problème multi-thread, il nous fallait un objet complexe à construire pour chaque thread. L'objet représente les données en cours de traitement et peut changer en fonction de la saisie de l'utilisateur.

Pourrions-nous utiliser une usine à la place? Oui

Pourquoi n'avons nous pas? Builder est plus logique, je suppose.

Les fabriques sont utilisées pour créer différents types d'objets qui sont du même type de base (implémentent la même interface ou la même classe de base).

Les constructeurs construisent le même type d'objet à plusieurs reprises, mais la construction est dynamique et peut donc être modifiée au moment de l'exécution.

10
Cameron MacFarland

En parcourant le framework Microsoft MVC, j'ai eu une idée du motif de construction. Je suis tombé sur le modèle dans la classe ControllerBuilder. Cette classe doit renvoyer la classe d'usine du contrôleur, qui est ensuite utilisée pour construire un contrôleur en béton.

L'avantage que je vois dans l'utilisation du modèle de générateur est que vous pouvez créer votre propre usine et la brancher dans le cadre.

@Tetha, il peut y avoir un restaurant (Framework) dirigé par un italien, qui sert des pizzas. Afin de préparer une pizza, un italien (Object Builder) utilise Owen (Factory) avec une base de pizza (classe de base).

Maintenant, un Indien prend le restaurant de l'italien. Restaurant indien (Framework) serveurs dosa au lieu de pizza. Afin de préparer dosa, un Indien (constructeur d’objets) utilise Poêle à frire (Usine) avec un Maida (classe de base)

Si vous regardez le scénario, la nourriture est différente, la façon dont la nourriture est préparée est différente, mais dans le même restaurant (dans le même cadre). Le restaurant doit être construit de manière à pouvoir servir une cuisine chinoise, mexicaine ou n’importe quelle cuisine. Le cadre intérieur de constructeur d’objets facilite l’installation du type de cuisine que vous souhaitez. par exemple

class RestaurantObjectBuilder
{
   IFactory _factory = new DefaultFoodFactory();

   //This can be used when you want to plugin the 
   public void SetFoodFactory(IFactory customFactory)
   {
        _factory = customFactory;
   }

   public IFactory GetFoodFactory()
   {
      return _factory;
   }
}
9
Nitin

Vous l'utilisez quand vous avez beaucoup d'options à traiter. Pensez à des choses comme jmock:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

Cela semble beaucoup plus naturel et est ... possible.

Il y a aussi la construction XML, la construction de chaînes et bien d'autres choses. Imaginez si Java.util.Map avait été créé en tant que constructeur. Vous pourriez faire des choses comme ça:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);
8
Dustin

S'appuyant sur les réponses précédentes (jeu de mots voulu), un excellent exemple concret est le support intégré de Groovy pour Builders.

Voir Constructeurs dans le Documentation Groovy

6
Ken Gentle

J'ai toujours détesté le motif Builder en tant que chose lourde, envahissante et très souvent abusée par des programmeurs moins expérimentés. C'est un modèle qui n'a de sens que si vous devez assembler l'objet à partir de certaines données qui nécessitent ne étape de post-initialisation (c'est-à-dire une fois que toutes les données ont été collectées - faites quelque chose avec. Dans 99% des cas, les constructeurs sont simplement utilisés pour initialiser les membres de la classe.

Dans de tels cas, il est de loin préférable de simplement déclarer withXyz(...) les modificateurs de type à l'intérieur de la classe et de leur faire renvoyer une référence à elle-même.

Considère ceci:

public class Complex {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first=first; 
    }

    ... 

    public Complex withFirst(String first){
       this.first=first;
       return this; 
    }

    public Complex withSecond(String second){
       this.second=second;
       return this; 
    }

    public Complex withThird(String third){
       this.third=third;
       return this; 
    }

}


Complex complex = new Complex()
     .withFirst("first value")
     .withSecond("second value")
     .withThird("third value");

Maintenant, nous avons une classe unique soignée qui gère sa propre initialisation et fait à peu près le même travail que le constructeur, sauf que c'est beaucoup plus élégant.

5
Pavel Lechev
/// <summary>
/// Builder
/// </summary>
public interface IWebRequestBuilder
{
    IWebRequestBuilder BuildHost(string Host);

    IWebRequestBuilder BuildPort(int port);

    IWebRequestBuilder BuildPath(string path);

    IWebRequestBuilder BuildQuery(string query);

    IWebRequestBuilder BuildScheme(string scheme);

    IWebRequestBuilder BuildTimeout(int timeout);

    WebRequest Build();
}

/// <summary>
/// ConcreteBuilder #1
/// </summary>
public class HttpWebRequestBuilder : IWebRequestBuilder
{
    private string _Host;

    private string _path = string.Empty;

    private string _query = string.Empty;

    private string _scheme = "http";

    private int _port = 80;

    private int _timeout = -1;

    public IWebRequestBuilder BuildHost(string Host)
    {
        _Host = Host;
        return this;
    }

    public IWebRequestBuilder BuildPort(int port)
    {
        _port = port;
        return this;
    }

    public IWebRequestBuilder BuildPath(string path)
    {
        _path = path;
        return this;
    }

    public IWebRequestBuilder BuildQuery(string query)
    {
        _query = query;
        return this;
    }

    public IWebRequestBuilder BuildScheme(string scheme)
    {
        _scheme = scheme;
        return this;
    }

    public IWebRequestBuilder BuildTimeout(int timeout)
    {
        _timeout = timeout;
        return this;
    }

    protected virtual void BeforeBuild(HttpWebRequest httpWebRequest) {
    }

    public WebRequest Build()
    {
        var uri = _scheme + "://" + _Host + ":" + _port + "/" + _path + "?" + _query;

        var httpWebRequest = WebRequest.CreateHttp(uri);

        httpWebRequest.Timeout = _timeout;

        BeforeBuild(httpWebRequest);

        return httpWebRequest;
    }
}

/// <summary>
/// ConcreteBuilder #2
/// </summary>
public class ProxyHttpWebRequestBuilder : HttpWebRequestBuilder
{
    private string _proxy = null;

    public ProxyHttpWebRequestBuilder(string proxy)
    {
        _proxy = proxy;
    }

    protected override void BeforeBuild(HttpWebRequest httpWebRequest)
    {
        httpWebRequest.Proxy = new WebProxy(_proxy);
    }
}

/// <summary>
/// Director
/// </summary>
public class SearchRequest
{

    private IWebRequestBuilder _requestBuilder;

    public SearchRequest(IWebRequestBuilder requestBuilder)
    {
        _requestBuilder = requestBuilder;
    }

    public WebRequest Construct(string searchQuery)
    {
        return _requestBuilder
        .BuildHost("ajax.googleapis.com")
        .BuildPort(80)
        .BuildPath("ajax/services/search/web")
        .BuildQuery("v=1.0&q=" + HttpUtility.UrlEncode(searchQuery))
        .BuildScheme("http")
        .BuildTimeout(-1)
        .Build();
    }

    public string GetResults(string searchQuery) {
        var request = Construct(searchQuery);
        var resp = request.GetResponse();

        using (StreamReader stream = new StreamReader(resp.GetResponseStream()))
        {
            return stream.ReadToEnd();
        }
    }
}

class Program
{
    /// <summary>
    /// Inside both requests the same SearchRequest.Construct(string) method is used.
    /// But finally different HttpWebRequest objects are built.
    /// </summary>
    static void Main(string[] args)
    {
        var request1 = new SearchRequest(new HttpWebRequestBuilder());
        var results1 = request1.GetResults("IBM");
        Console.WriteLine(results1);

        var request2 = new SearchRequest(new ProxyHttpWebRequestBuilder("localhost:80"));
        var results2 = request2.GetResults("IBM");
        Console.WriteLine(results2);
    }
}
5
Raman Zhylich

Un autre avantage du constructeur est que si vous avez une fabrique, il y a toujours un couplage dans votre code, car pour que la fabrique fonctionne, elle doit connaître tous les objets qu’elle peut éventuellement créer. Si vous ajoutez un autre objet pouvant être créé, vous devrez modifier la classe d'usine pour l'inclure. Cela se produit également dans l’abstrait Factory.

Avec le constructeur, par contre, il vous suffit de créer un nouveau constructeur de béton pour cette nouvelle classe. La classe de direction restera la même, car elle reçoit le constructeur dans le constructeur.

En outre, il existe de nombreuses saveurs de constructeur. Kamikaze Mercenary en donne un autre.

5
Lino Rosa

J'ai utilisé le constructeur dans la bibliothèque de messagerie maison. Le noyau de la bibliothèque recevait des données du réseau et les collectait avec l’instance Builder, puis, une fois que Builder avait décidé de disposer de tout le nécessaire pour créer une instance Message, Builder.GetMessage () était en train de construire une instance de message en utilisant les données collectées à partir du fichier. câble.

3
wasker

Lorsque j'ai voulu utiliser le XMLGregorianCalendar standard pour mon XML afin de structurer les objets de DateTime en Java, j'ai entendu de nombreux commentaires sur le poids et la lourdeur de son utilisation. J'essayais de contrôler les champs XML dans les structures xs: datetime pour gérer les fuseaux horaires, les millisecondes, etc.

J'ai donc conçu un utilitaire pour construire un calendrier XMLGregorian à partir d'un GregorianCalendar ou de Java.util.Date.

En raison de mon lieu de travail, je ne suis pas autorisé à le partager en ligne sans autorisation légale, mais voici un exemple d'utilisation d'un client. Il résume les détails et filtre certaines des implémentations de XMLGregorianCalendar qui sont moins utilisées pour xs: datetime.

XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();

Si ce modèle est plus un filtre, car il définit les champs de xmlCalendar comme non définis afin qu'ils soient exclus, il le "construit" quand même. J'ai facilement ajouté d'autres options au générateur pour créer une structure xs: date et xs: time, ainsi que pour manipuler les décalages de fuseau horaire en cas de besoin.

Si vous avez déjà vu du code qui crée et utilise XMLGregorianCalendar, vous verrez comment cela est beaucoup plus facile à manipuler.

2
John Brown

Découvrez InnerBuilder, un plug-in IntelliJ IDEA qui ajoute une action "Générateur" au menu Générer (Alt + Inser), qui génère une classe de générateur interne, comme décrit dans Effective Java.

https://github.com/analytically/innerbuilder

2
analytically

Un bon exemple du monde réel consiste à utiliser lorsque vous testez vos classes. Vous utilisez les générateurs sut (System Under Test).

Exemple:

Classe:

public class CustomAuthenticationService
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationService(ICloudService cloudService, IDatabaseService databaseService)
    {
        _cloudService = cloudService;
        _databaseService = databaseService;
    }

    public bool IsAuthorized(User user)
    {            
        //Implementation Details
        return true;

}

Tester:

    [Test]
    public void Given_a_User_With_Permission_When_Verifying_If_Authorized_Then_Authorize_It_Returning_True()
    {
        CustomAuthenticationService sut = new CustomAuthenticationServiceBuilder();
        User userWithAuthorization = null;

        var result = sut.IsAuthorized(userWithAuthorization);

        Assert.That(result, Is.True);
    }

constructeur:

public class CustomAuthenticationServiceBuilder
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationServiceBuilder()
    {
        _cloudService = new AwsService();
        _databaseService = new SqlServerService();
    }

    public CustomAuthenticationServiceBuilder WithAzureService(AzureService azureService)
    {
        _cloudService = azureService;

        return this;
    }

    public CustomAuthenticationServiceBuilder WithOracleService(OracleService oracleService)
    {
        _databaseService = oracleService;

        return this;
    }

    public CustomAuthenticationService Build()
    {
        return new CustomAuthenticationService(_cloudService, _databaseService);
    }

    public static implicit operator CustomAuthenticationService (CustomAuthenticationServiceBuilder builder)
    {
        return builder.Build();
    }
}
0
Rafael Miceli