代码之家  ›  专栏  ›  技术社区  ›  Charles Graham

您什么时候会使用生成器模式?[闭门]

  •  479
  • Charles Graham  · 技术社区  · 17 年前

    常见的 , 如何使用生成器模式?它给你买了什么?为什么不使用工厂模式呢?

    15 回复  |  直到 11 年前
        1
  •  1081
  •   usr-local-ΕΨΗΕΛΩΝ    13 年前

    设计模式 . 在Java中使用它的原因也适用于其他编程语言。

    正如约书亚·布洛赫在书中所说的那样 Effective Java, 2nd Edition :

    当设计其构造函数或静态工厂将具有多于几个参数的类时,生成器模式是一个很好的选择。

    我们都曾经遇到过一个类,它有一个构造函数列表,每次添加都会添加一个新的选项参数:

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

    这称为伸缩构造函数模式。 这个模式的问题是,一旦构造函数有4或5个参数长,它就变成了 难记 必要的 参数的顺序 以及在给定情况下可能需要的特定构造函数。

    可供替代的 JavaBean模式 其中,您使用强制参数调用构造函数,然后在以下情况下调用任何可选设置程序:

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

    更好的选择是使用生成器模式。

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

    注意 . 因为生成器的setter方法返回生成器对象,所以它们是 能被锁住 .

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

    在本例中 可以修改构建方法 将参数从生成器复制到Pizza对象后检查参数,并 如果提供了无效的参数值,则引发IllegalStateException。 这种模式是灵活的,将来很容易向其添加更多参数。只有当一个构造函数的参数超过4或5个时,它才真正有用。尽管如此,从一开始这可能是值得的 如果您怀疑将来可能会添加更多参数。

    关于这个话题,我从这本书中借了很多东西 有效Java,第二版 我极力推荐。

        2
  •  334
  •   Pops Atula    13 年前

    考虑一家餐馆。“今天的饭菜”的创建是一种工厂模式,因为你告诉厨房“给我今天的饭菜”,厨房(工厂)根据隐藏的标准决定生成什么对象。

    如果您订购自定义比萨饼,则会显示生成器。在这种情况下,服务员告诉厨师(构建者)“我需要一个比萨饼;在比萨饼中添加奶酪、洋葱和培根!”因此,构建者公开了生成的对象应该具有的属性,但隐藏了如何设置它们。

        3
  •  278
  •   Ajay    7 年前

    生成器和factory IMHO的关键区别在于,当您需要做很多事情来构建对象时,生成器非常有用。例如,想象一个DOM。您必须创建大量节点和属性才能获得最终对象。当工厂可以在一个方法调用中轻松创建整个对象时,使用工厂。

    (未显示参数) :

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

    退房 Builder Pattern on Wikipedia .

        4
  •  19
  •   dssfsdf dssfsdf    17 年前

    .NET StringBuilder类是生成器模式的一个很好的示例。它主要用于在一系列步骤中创建字符串。执行ToString()的最终结果始终是一个字符串,但该字符串的创建取决于StringBuilder类中使用的函数。总之,基本思想是构建复杂对象,并隐藏如何构建的实现细节。

        5
  •  11
  •   Cameron MacFarland    17 年前

    对于多线程问题,我们需要为每个线程构建一个复杂的对象。对象表示正在处理的数据,并且可以根据用户输入而更改。

    我们可以用工厂来代替吗?对

    为什么我们没有?我想建设者更有意义。

    工厂用于创建相同基本类型的不同类型的对象(实现相同的接口或基类)。

    构建器反复构建同一类型的对象,但构造是动态的,因此可以在运行时更改。

        6
  •  11
  •   Pavel Lechev    11 年前

    我一直不喜欢Builder模式,因为它笨重、突兀,而且经常被经验不足的程序员滥用。它是一种模式,只有当您需要从需要的数据中组装对象时才有意义 (即,一旦收集到所有数据,就用它做点什么)。相反,在99%的时间里,构建器只是用来初始化类成员。

    withXyz(...)

    考虑一下:

    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");
    

        7
  •  10
  •   Dustin    17 年前

    当你有很多选择要处理时,你就使用它。想想像jmock这样的事情:

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

    这感觉更自然,而且是……可能的。

    还有xml构建、字符串构建和许多其他东西。想象一下如果 java.util.Map 作为一个建设者,他把。你可以这样做:

    Map<String, Integer> m = new HashMap<String, Integer>()
        .put("a", 1)
        .put("b", 2)
        .put("c", 3);
    
        8
  •  9
  •   Buhake Sindi Tesnep    14 年前

    我看到使用builder模式的优点是,您可以创建自己的工厂并将其插入框架。

    @特塔,可以有一家由意大利人经营的餐厅(框架),提供比萨饼。为了准备比萨饼,意大利人guy(Object Builder)使用Owen(Factory)和比萨饼基(基类)。

    现在印度佬从意大利佬手中接管了餐厅。印度餐厅(框架)提供dosa而不是比萨饼。为了准备dosa Indian guy(对象生成器)使用带有Maida(基类)的煎锅(工厂)

    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
  •  7
  •   Laurel Enrique    9 年前
        10
  •  6
  •   Lino Rosa    16 年前

    构建器的另一个优点是,如果您有一个工厂,那么代码中仍然存在一些耦合,因为 对于工厂来说,它必须知道它可能创建的所有对象 . 如果添加另一个可以创建的对象,则必须修改factory类以包含该对象。这种情况也发生在抽象工厂中。

    此外,还有许多风格的建筑商。Kamikaze雇佣军公司提供了另一个。

        11
  •  5
  •   Raman Zhylich    12 年前
    /// <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);
        }
    }
    
        12
  •  3
  •   wasker    17 年前

    我在自己开发的消息传递库中使用了builder。库核心从连线接收数据,使用Builder实例收集数据,然后,一旦Builder确定它已经获得了创建消息实例所需的一切,Builder.GetMessage()将使用从连线收集的数据构造消息实例。

        13
  •  2
  •   JackPoint Development 4.0    12 年前

    因为我工作的地方,我不允许在没有法律许可的情况下在线分享,但这里有一个客户如何使用它的例子。它抽象了XMLGregorianCalendar的细节,并过滤了XMLGregorianCalendar的一些实现,这些实现很少用于xs:datetime。

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

    假设这个模式更像是一个过滤器,因为它将xmlCalendar中的字段设置为未定义的,所以它们被排除在外,它仍然“构建”它。我很容易地向构建器添加了其他选项,以创建xs:date和xs:time结构,并在需要时操作时区偏移。

    如果您曾经看到过创建和使用XMLGregorianCalendar的代码,您将看到这如何使其更易于操作。

    推荐文章