Когда вы будете использовать шаблон Builder?

Каковы некоторые общие, реальные примеры использования шаблона Builder? Что он покупает? Почему бы просто не использовать шаблон Factory?

Ответ 1

Ключевое различие между сборщиком и фабрикой IMHO заключается в том, что сборщик полезен, когда вам нужно много чего сделать, чтобы построить объект. Например, представьте DOM. Вы должны создать множество узлов и атрибутов, чтобы получить конечный объект. Фабрика используется, когда фабрика может легко создать весь объект за один вызов метода.

Одним из примеров использования компоновщика является создание XML-документа. Я использовал эту модель при построении фрагментов HTML, например, у меня может быть Builder для построения таблицы определенного типа, и он может иметь следующие методы (параметры не показаны).:

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

Затем этот конструктор выложит мне HTML. Это намного легче читать, чем проходить через большой процедурный метод.

Проверьте Образец Строителя в Википедии.

Ответ 2

Ниже приводятся некоторые аргументы в пользу использования шаблона и кода примера в Java, но это реализация шаблона Builder, охваченного Группой четырех в шаблонах проектирования. Причины, по которым вы будете использовать его в Java, также применимы и к другим языкам программирования.

Как говорит Джошуа Блох в Эффективная 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 параметров, он становится трудно запоминающимся требуемым порядок параметров, а также какой конкретный конструктор вы можете захотеть в данной ситуации.

Один альтернативный для шаблона конструктора Telescoping - это шаблон JavaBean, где вы вызываете конструктор с обязательными параметрами, а затем вызываете любые дополнительные сеттеры после:

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

Проблема заключается в том, что, поскольку объект создается по нескольким вызовам, он может находиться в несогласованном состоянии частично через его конструкцию. Это также требует больших усилий для обеспечения безопасности потоков.

Лучшей альтернативой является использование шаблона Builder.

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

Обратите внимание, что Пицца неизменна и все значения параметров находятся в одном месте. Поскольку методы set Builder возвращают объект Builder, они могут быть привязаны.

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

В результате получается код, который легко писать и очень легко читать и понимать. В этом примере метод build может быть изменен, чтобы проверить параметры после того, как они был скопирован из конструктора в объект Pizza и выбросил IllegalStateException, если было предоставлено недопустимое значение параметра. Этот шаблон является гибким, и в будущем его легко добавить в него. Это действительно полезно, если у вас будет более 4 или 5 параметров для конструктора. Тем не менее, это может быть полезно в первую очередь , если вы подозреваете, что можете добавить в будущем дополнительные параметры.

Я серьезно занялся этой темой из книги "Эффективная Java", второе издание Джошуа Блоха. Чтобы узнать больше об этом шаблоне и других эффективных практиках Java , я настоятельно рекомендую его.

Ответ 3

Рассмотрим ресторан. Создание "сегодняшней еды" - это шаблон factory, потому что вы говорите кухне "принесите мне сегодня еду", а кухня (factory) решает, какой объект сгенерировать на основе скрытых критериев.

Создатель появляется, если вы заказываете собственную пиццу. В этом случае официант говорит шеф-повару (строителю): "Мне нужна пицца, добавьте сыр, лук и бекон!" Таким образом, построитель предоставляет атрибуты, которые должен иметь сгенерированный объект, но скрывает, как их устанавливать.

Ответ 4

Класс .NET StringBuilder - отличный пример шаблона построителя. Он в основном используется для создания строки в последовательности шагов. Конечный результат, который вы получаете при выполнении ToString(), всегда является строкой, но создание этой строки зависит от того, какие функции в классе StringBuilder использовались. Подводя итог, основная идея состоит в том, чтобы создавать сложные объекты и скрывать детали реализации того, как он строится.

Ответ 5

Для многопоточной задачи нам понадобился сложный объект для каждого потока. Объект представлял обрабатываемые данные и мог меняться в зависимости от пользовательского ввода.

Можно ли использовать factory? Да

Почему не мы? Думаю, Builder имеет больше смысла.

Заводы используются для создания различных типов объектов, которые являются одним и тем же базовым типом (реализуют один и тот же интерфейс или базовый класс).

Строители строят один и тот же тип объекта снова и снова, но построение является динамическим, поэтому его можно изменять во время выполнения.

Ответ 6

Вы используете его, когда у вас есть много вариантов для решения. Подумайте о таких вещах, как 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);

Ответ 7

Во время прохождения через Microsoft MVC я подумал о шаблоне компоновщика. Я столкнулся с шаблоном в классе ControllerBuilder. Этот класс должен вернуть класс контроллера factory, который затем используется для сборки конкретного контроллера.

Преимущество, которое я вижу в использовании шаблона компоновщика, заключается в том, что вы можете создать свой собственный factory и подключить его к структуре.

@Tetha, может быть ресторан (Рамка), управляемый итальянским парнем, который обслуживает Пиццу. Для подготовки пиццы итальянский парень (Object Builder) использует Owen (Factory) с базой пиццы (базовый класс).

Теперь индийский парень берет ресторан у итальянского парня. Индийский ресторан (рамочный) серверы доса вместо пиццы. Для подготовки доса индийский парень (строитель объектов) использует Frying Pan (Factory) с 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

Еще одно преимущество конструктора заключается в том, что если у вас есть Factory, в вашем коде есть еще какая-то связь, потому что для Factory работать, он должен знать все объекты, которые он может создать. Если вы добавите другой объект, который может быть создан, вам придется изменить класс Factory, чтобы включить его. Это также происходит в тесте Factory.

С создателем, с другой стороны, вам просто нужно создать новый конкретный строитель для этого нового класса. Класс директора останется таким же, потому что он получает конструктор в конструкторе.

Кроме того, есть много вкусов строителя. Наемник Камикадзе дает еще один.

Ответ 10

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

Ответ 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");

Теперь у нас есть аккуратный единственный класс, который управляет своей собственной инициализацией и выполняет в значительной степени ту же работу, что и строитель, за исключением того, что он намного более изящный.

Ответ 12

Я использовал построитель в библиотеке сообщений для домашних пользователей. Ядро библиотеки получало данные из проводника, собирая его с помощью экземпляра Builder, а затем, когда Builder решил, что у него есть все необходимое для создания экземпляра Message, Builder.GetMessage() создавал экземпляр сообщения, используя данные, собранные из провода.

Ответ 13

Когда я хотел использовать стандартный XMLGregorianCalendar для моего XML для сортировки объектов DateTime на Java, я услышал много комментариев о том, насколько тяжелым и громоздким было его использование. Я пытался сопоставить XML-поля в структурах xs: datetime для управления часовым поясом, миллисекундами и т.д.

Итак, я разработал утилиту для создания XMLGregorian календаря из GregorianCalendar или java.util.Date.

Из-за того, где я работаю, мне не разрешено делиться ею онлайн без законных, но вот пример того, как клиент ее использует. Он абстрагирует детали и фильтрует некоторую реализацию XMLGregorianCalendar, которые менее используются для xs: datetime.

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

Предоставлен этот шаблон больше из фильтра, поскольку он устанавливает поля в xmlCalendar как undefined, поэтому они исключены, он все еще "строит" его. Я легко добавил другие конструкторы в конструктор для создания xs: date и xs: time struct, а также для управления смещениями часового пояса, когда это необходимо.

Если вы когда-либо видели код, который создает и использует XMLGregorianCalendar, вы увидите, как это упростило управление.

Ответ 14

Проверьте InnerBuilder, плагин IntelliJ IDEA, который добавляет действие "Builder" в меню Generate (Alt + Insert), которое генерирует класс внутреннего строителя, как описано в "Эффективной Java"

https://github.com/analytically/innerbuilder

Ответ 15

Хорошим примером для реального мира является использование при тестировании модулей. Вы используете строители sut (System Under Test).

Пример:

Класс:

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

sut Builder:

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