Назвіть декілька загальних , реальних прикладів використання моделі Builder? Що це купує у вас? Чому б просто не використовувати заводський візерунок?
Назвіть декілька загальних , реальних прикладів використання моделі Builder? Що це купує у вас? Чому б просто не використовувати заводський візерунок?
Відповіді:
Ключова відмінність будівельника від фабричного ІМХО полягає в тому, що будівельник корисний, коли потрібно зробити багато речей для створення об'єкта. Наприклад, уявіть собі DOM. Вам потрібно створити безліч вузлів і атрибутів, щоб отримати ваш кінцевий об'єкт. Фабрика використовується, коли фабрика може легко створити весь об'єкт в межах одного виклику методу.
Одним із прикладів використання будівельника є побудова XML-документа. Я використовував цю модель під час створення фрагментів HTML, наприклад, у мене може бути Builder для створення конкретного типу таблиці, і він може мати такі методи (параметри не показані) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Потім цей конструктор виплюнув би мені HTML. Це читати набагато простіше, ніж ходити через великий процедурний метод.
Ознайомтеся з шаблоном Builder у Вікіпедії .
Нижче наведено декілька причин, за якими аргументовано використання шаблону та коду прикладу на Java, але це реалізація Шаблону Builder, охопленого бандою чотирьох у шаблонах дизайну . Причини, якими ви користуєтесь ним на Java, також застосовні і до інших мов програмування.
Як заявляє Джошуа Блох в Ефективній Java, 2-е видання :
Шаблон будівельника є хорошим вибором при проектуванні класів, конструктори чи статичні заводи матимуть більше ніж декілька параметрів.
Ми всі в якийсь момент стикалися з класом зі списком конструкторів, де кожне додавання додає новий параметр параметра:
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
Для багатопотокової проблеми нам знадобився складний об'єкт для кожного потоку. Об'єкт представляв дані, що обробляються, і може змінюватися залежно від введених даних користувачів.
Чи могли б ми замість цього використати фабрику? Так
Чому ми не стали? Думаю, Builder має більше сенсу.
Фабрики використовуються для створення різних типів об'єктів, що мають однаковий базовий тип (реалізують той же інтерфейс або базовий клас).
Будівельники будують один і той же тип об’єктів знову і знову, але конструкція динамічна, тому її можна змінювати під час виконання.
Ви використовуєте його, коли у вас є багато варіантів, з якими можна впоратися. Подумайте про такі речі, як 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) використовує Оуена (Фабрика) з основою для піци (базовий клас).
Зараз індійський хлопець переймає ресторан від італійського хлопця. Індійський ресторан (Framework) серверів dosa замість піци. Для підготовки доси індійський хлопець (будівельник об'єктів) використовує Сковороду (Фабрика) з Майдою (базовий клас)
Якщо дивитися на сценарій, їжа відрізняється, спосіб приготування їжі відрізняється, але в тому ж ресторані (за одними рамками). Ресторан повинен бути побудований таким чином, щоб він міг підтримувати китайську, мексиканську або будь-яку кухню. Конструктор об'єктів всередині рамки полегшує додавання потрібної кухні кухні. наприклад
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");
Зараз у нас є акуратний єдиний клас, який керує власною ініціалізацією і виконує майже таку ж роботу, як і будівельник, за винятком того, що його набагато елегантніше.
Ще одна перевага будівельника полягає в тому, що якщо у вас є Завод, у вас ще є якась муфта, оскільки для роботи Фабрики він повинен знати всі об'єкти, які він може створити . Якщо ви додасте інший об’єкт, який можна було б створити, вам доведеться змінити заводський клас, щоб включити його. Це трапляється і на абстрактній фабриці.
З будівельника, з іншого боку, вам просто потрібно створити нового бетонного будівельника для цього нового класу. Клас режисера залишиться тим самим, оскільки він отримує будівельника в конструкторі.
Також є багато ароматів будівельника. 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);
}
}
Спираючись на попередні відповіді (призначені каламбури), відмінним реальним прикладом є побудований Groovy для підтримки Builders
.
MarkupBuilder
StreamingMarkupBuilder
SwingXBuilder
Дивіться будівельників у Groovy Документації
Я використовував будівельник у домашній бібліотеці обміну повідомленнями. Ядро бібліотеки отримувало дані з дроту, збираючи їх за допомогою екземпляра Builder. Потім, як тільки Builder вирішив, що все, що потрібно для створення екземпляра повідомлення, Builder.GetMessage () будував екземпляр повідомлення, використовуючи дані, зібрані з дріт.
Ознайомтеся з InnerBuilder, плагіном IntelliJ IDEA, який додає дію "Builder" до меню "Створити" (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 structure, а також для маніпулювання зрушеннями часового поясу при необхідності.
Якщо ви коли-небудь бачили код, який створює та використовує 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);
}
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();
}
}
CustomAuthenticationService
клас?