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

StackOverflow https://stackoverflow.com/questions/328496

  •  11-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

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

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

Ознакомьтесь с шаблоном построителя в Википедии .

Другие советы

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

Как утверждает Джошуа Блох в Эффективной Java, 2-е издание :

  

Шаблон конструктора является хорошим выбором при разработке классов, у конструкторов или статических фабрик которых будет больше, чем несколько параметров.

В какой-то момент все мы столкнулись с классом со списком конструкторов, где каждое добавление добавляет новый параметр 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) { ... }

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

Одна альтернатива , которая у вас есть для шаблона телескопического конструктора, - это шаблон 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;
  }
}

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

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

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

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

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

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

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

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

Можем ли мы использовать фабрику вместо этого? Да

Почему не мы? Строитель имеет больше смысла, я думаю.

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

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

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

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

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

Теперь индийский парень принимает ресторан у итальянского парня. Индийский ресторан (Framework) на серверах доши вместо пиццы. Чтобы подготовить досу, индийский парень (строитель объектов) использует Сковороду (Фабрика) с Майдой (базовый класс)

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

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

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

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

Видеть Строители в Groovy-документация

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

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

Кроме того, есть много ароматов строителя. Kamikaze Mercenary`s дает еще один.

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

Мне всегда не нравился шаблон 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");

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

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

Когда я захотел использовать стандартный 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 как неопределенные, поэтому они исключаются, он все еще "строит" Это. Я легко добавил другие параметры в конструктор для создания структуры xs: date и xs: time, а также для управления смещениями часового пояса при необходимости.

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

Ознакомьтесь с InnerBuilder, плагином IntelliJ IDEA, который добавляет действие «Строитель» в меню «Создать» (Alt + Insert), которое генерирует внутренний класс построителя, как описано в разделе «Эффективная Java»

https://github.com/analytical/innerbuilder

Отличным примером из реальной жизни является использование при модульном тестировании ваших классов.Вы используете сборщики sut (тестируемая система).

Пример:

Сорт:

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

строитель сут:

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();
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top