Каковы некоторые распространенные , реальные примеры использования шаблона Builder? Что он тебе покупает? Почему бы просто не использовать фабричный шаблон?
Каковы некоторые распространенные , реальные примеры использования шаблона Builder? Что он тебе покупает? Почему бы просто не использовать фабричный шаблон?
Ответы:
Ключевое различие между сборщиком и фабрикой IMHO заключается в том, что сборщик полезен, когда вам нужно много чего сделать, чтобы построить объект. Например, представьте DOM. Вы должны создать множество узлов и атрибутов, чтобы получить конечный объект. Фабрика используется, когда фабрика может легко создать весь объект за один вызов метода.
Одним из примеров использования компоновщика является создание XML-документа. Я использовал эту модель при построении фрагментов HTML, например, у меня может быть Builder для построения таблицы определенного типа, и он может иметь следующие методы (параметры не показаны). :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Затем этот конструктор выложит мне HTML. Это гораздо легче прочитать, чем пройти через большой процедурный метод.
Проверьте Шаблон Строителя в Википедии .
Ниже приведены некоторые причины, приводящие доводы в пользу использования шаблона и примера кода в Java, но это реализация шаблона Builder, охватываемого бандой четырех в шаблонах проектирования. . Причины, по которым вы будете использовать его в Java, также применимы и к другим языкам программирования.
Как утверждает Джошуа Блох в Effective Java, 2nd Edition :
Шаблон конструктора является хорошим выбором при разработке классов, чьи конструкторы или статические фабрики будут иметь больше, чем несколько параметров.
В какой-то момент мы все столкнулись с классом со списком конструкторов, где каждое добавление добавляет новый параметр 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;
}
}
Обратите внимание, что Pizza является неизменным и все значения параметров находятся в одном месте . Поскольку методы сеттера Builder возвращают объект Builder, они могут быть связаны .
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
В результате получается код, который легко написать, а также легко прочитать и понять. В этом примере метод сборки может быть изменен для проверки параметров после их копирования из компоновщика в объект Pizza и создания исключения IllegalStateException, если указано недопустимое значение параметра. Этот шаблон является гибким, и в будущем к нему легко добавить больше параметров. Это действительно полезно, только если у вас будет более 4 или 5 параметров для конструктора. Тем не менее, это может быть стоит в первую очередь если вы подозреваете, что, возможно, добавите больше параметров в будущем.
Я много позаимствовал на эту тему из книги Джошуа Блоха « Эффективная Java», 2-е издание . Чтобы больше узнать об этом шаблоне и других эффективных методах Java, я настоятельно рекомендую его.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
вам нужно будет перекомпилировать свой код или иметь ненужную логику, если вам нужны только пепперони пицц. По крайней мере, вы также должны предоставить параметризованные версии, такие как первоначально предложенный @Kamikaze Mercenary. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
, Опять же, мы никогда не тестируем юнит, не так ли?
Рассмотрим ресторан. Создание «сегодняшней еды» - это фабричный шаблон, потому что вы говорите кухне «принесите мне сегодняшнюю еду», а кухня (фабрика) решает, какой объект генерировать, основываясь на скрытых критериях.
Строитель появится, если вы закажете заказную пиццу. В этом случае официант говорит шеф-повару (строителю): «Мне нужна пицца; добавь в нее сыр, лук и бекон!» Таким образом, конструктор предоставляет атрибуты, которые должен иметь сгенерированный объект, но скрывает, как их устанавливать.
Класс .NET StringBuilder является отличным примером шаблона компоновщика. В основном это используется для создания строки в серии шагов. Конечный результат, который вы получаете при выполнении ToString (), всегда является строкой, но создание этой строки зависит от того, какие функции в классе StringBuilder были использованы. Подводя итог, основная идея состоит в том, чтобы создавать сложные объекты и скрывать детали реализации того, как он создается.
b.append(...).append(...)
перед окончательным вызовом toString()
. Образец цитирования: infoq.com/articles/internal-dsls-java
Для многопоточной задачи нам нужно было создать сложный объект для каждого потока. Объект представляет обрабатываемые данные и может меняться в зависимости от ввода пользователя.
Можем ли мы использовать фабрику вместо этого? да
Почему не мы? Строитель имеет больше смысла, я думаю.
Фабрики используются для создания различных типов объектов, имеющих одинаковый базовый тип (реализуют один и тот же интерфейс или базовый класс).
Строители строят один и тот же тип объекта снова и снова, но конструкция является динамичной, поэтому ее можно изменять во время выполнения.
Вы используете его, когда у вас есть много вариантов для решения. Думайте о вещах как 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);
Проходя через фреймворк 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;
}
}
Мне всегда не нравился шаблон 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");
Теперь у нас есть аккуратный отдельный класс, который управляет собственной инициализацией и выполняет почти ту же работу, что и конструктор, за исключением того, что он гораздо более элегантен.
Другое преимущество компоновщика состоит в том, что если у вас есть Фабрика, в вашем коде все еще есть некоторая связь, потому что для того, чтобы Фабрика работала, она должна знать все объекты, которые она может создать . Если вы добавите еще один объект, который может быть создан, вам придется изменить класс фабрики, чтобы включить его. Это происходит и в абстрактной фабрике.
С другой стороны, для строителя вам просто нужно создать нового бетоностроителя для этого нового класса. Класс 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);
}
}
Основываясь на предыдущих ответах (каламбур), отличным примером из реальной жизни является встроенная поддержка GroovyBuilders
.
MarkupBuilder
StreamingMarkupBuilder
SwingXBuilder
См. Builders в Groovy документации
Я использовал builder в домашней библиотеке сообщений. Ядро библиотеки получало данные из сети, собирая их с помощью экземпляра Builder, а затем, как только Builder решил, что у него есть все необходимое для создания экземпляра Message, Builder.GetMessage () создавал экземпляр сообщения с использованием данных, собранных из провод.
Посмотрите InnerBuilder, плагин IntelliJ IDEA, который добавляет действие «Строитель» в меню «Создать» (Alt + Insert), которое генерирует внутренний класс построителя, как описано в разделе «Эффективная Java».
Когда я захотел использовать стандартный 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, вы бы увидели, как это облегчает манипулирование.
Отличным примером из реальной жизни является использование при модульном тестировании ваших классов. Вы используете сборщики 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);
}
Сул Строитель:
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();
}
}
CustomAuthenticationService
класс?