Question

Quels sont certains exemples communs, d'exemples concrets 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?

Était-ce utile?

La solution

La principale différence entre un constructeur et un IMHO de fabrique 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 nœuds et attributs pour obtenir votre objet final. Une fabrique est utilisée lorsque la fabrique peut facilement créer l'objet entier au sein d'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 alors le code HTML pour moi. C’est beaucoup plus facile à lire que de parcourir une méthode procédurale volumineuse.

Découvrez le modèle de générateur sur Wikipedia .

Autres conseils

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

Comme le dit Joshua Bloch dans Effective Java, 2e édition :

  

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) { ... }

Ce modèle s'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 rappeler le requis. ordre des paramètres , ainsi que le constructeur que vous souhaitez obtenir dans une situation donnée.

Une alternative au modèle de constructeur télescopique est le modèle JavaBean dans lequel vous appelez un constructeur avec les paramètres obligatoires, puis tous les paramètres optionnels suivants:

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

Le problème est que, comme l'objet est créé lors de plusieurs appels, il se peut qu'il soit dans un état incohérent tout au long de sa construction. Cela nécessite également beaucoup d'efforts supplémentaires pour garantir la sécurité des threads.

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 Builder, elles sont capables d'être chaînées .

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

Cela donne un code facile à écrire, très facile à lire et à comprendre. Dans cet exemple, la méthode de génération peut être modifiée pour vérifier les paramètres après leur été copié du générateur vers 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 ultérieurement des paramètres. Ce n'est vraiment utile que si vous allez avoir plus de 4 ou 5 paramètres pour un constructeur. Cela dit, il vaut peut-être la peine de commencer si vous pensez que vous ajouterez peut-être d'autres paramètres à l'avenir.

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.

Considérons un restaurant. La création du "repas du jour" est un modèle d’usine, parce que vous dites à la cuisine "donnez-moi le repas du jour" & et la cuisine (usine) décide quel objet générer, en fonction de critères cachés.

Le générateur apparaît si vous commandez une pizza personnalisée. Dans ce cas, le serveur indique au chef (constructeur) "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.

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 manière dont ils sont construits.

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.

Peut-on 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 à l'exécution.

En parcourant le framework Microsoft MVC, j’ai réfléchi au 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 concret.

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 la relève du restaurant 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’ajout 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;
   }
}

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é mis en tant que générateur. Vous pouvez faire des choses comme ceci:

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

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

Voir Constructeurs dans la section Documentation Groovy

Un autre avantage du constructeur est que, si vous avez une fabrique, il y a encore un couplage dans votre code, car pour que la fabrique fonctionne, il doit connaître tous les objets qu'il 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 en 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 variantes de constructeur. Kamikaze Mercenary en donne un autre.

/// <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);
    }
}

J'ai toujours détesté le motif Builder car il était quelque chose de lourd, importun et très souvent abusé 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 nécessitant une é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 (...) dans la classe et de leur faire renvoyer une référence à elle-même.

Considérez 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");

Nous avons maintenant une classe simple qui gère sa propre initialisation et fait à peu près le même travail que le constructeur, à l'exception de son beaucoup plus élégante.

J'ai utilisé Builder dans une bibliothèque de messagerie locale. 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. fil.

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 créer un calendrier XMLGregorian à partir d'un calendrier 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();

Accordé, ce modèle est davantage un filtre car il définit les champs de xmlCalendar comme non définis, de sorte qu'ils sont exclus, il reste "construit". il. 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 facilite grandement la manipulation.

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

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;

}

Test:

    [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();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top