Обычно данный паттерн применяют тогда, когда алгоритм конструирования какого-то сложного объекта не зависит от его частей и от их взаимодействия друг с другом. Но при его реализации должны обеспечиваться различные варианты представления объекта. Ниже дана общая схема паттерна.
Рассмотрим участников шаблона:
Builder — это, собственно, и есть Строитель, представляющий собой абстрактный интерфейс для создания объекта Product по частям;
ConcreteBuilder — конкретный интерфейс Строителя. Если вы решите, что ваш Строитель собирает ПК, то конкретные строители могут создавать компьютеры разных марок, например Apple, Acer или Hewlett-Packard;
Director — класс, занимающийся конструированием объекта, используя интерфейс, представленный Строителем;
Product — итоговый продукт, получающийся в результате конструирования.
Родственным паттерном Строителя исторически считается Абстрактная фабрика, так как оба шаблона предназначены для конструирования сложных объектов. Однако в то время как Абстрактная фабрика создает семейства объектов, Строитель направлен на конструирование какого-либо одного сложного объекта.
А теперь, чтобы лучше понять суть паттерна Builder, рассмотрим практический пример, в котором вы будете собирать объект HappyMeal — знаменитый детский набор, предлагаемый в сети закусочных McDonald’s. В первом случае вы соберете бюджетный вариант HappyMeal (маленькая порция пепси-колы, гамбургер, картошка и игрушка), а во втором — BigHappyMeal (гамбургер вы замените на бигмак и увеличите порцию напитка).
Начните с реализации продукта:
class HappyMeal
{
// содержит информацию о // составе HappyMeal
ArrayList parts = new ArrayList();
// Добавляете информацию // о новой составной части
public void Add(string part)
{
parts.Add(part);
}
// Выводите информацию о // всем наборе
public void Show()
{
Console.WriteLine(" Happy Meal Parts ——-");
foreach (string part in parts)
Console.WriteLine (part);
}
}
В этой части работы нет ничего сложного. Все данные о составе HappyMeal будут храниться в массиве ArrayList (удостоверьтесь, что в списке using присутствует System.Collections). С помощью метода Add добавьте данные в ArrayList. Метод Show просто «проходит» по вашему объекту, выводя строку за строкой.
Теперь реализуйте Строитель:
// "Строитель — абстрактный интерфейс для создания объекта HappyMeal по частям"
abstract class HappyMealBuilder
{
public abstract void BuildBurger();
public abstract void BuildPepsi();
public abstract void BuildFries();
public abstract void BuildToy();
public abstract HappyMeal GetProduct();
}
Всего получилось пять абстрактных методов. Первые четыре (начинающиеся со слов Build) как раз и предназначены для конструирования какой-то части вашего объекта. Из их названий понятно, что они создают. Пятый (называющийся GetProduct) просто возвращает сконструированный вами HappyMeal.
Теперь реализуйте свой первый конкретный Строитель, создающий большой HappyMeal:
// Строитель для большого // HappyMeal
class BigHappyMealBuilder : HappyMealBuilder
{
private HappyMeal happy_meal = new HappyMeal();
public override void BuildBurger()
{
happy_meal.Add("BigMac");
}
public override void BuildPepsi()
{
happy_meal.Add("Pepsi 0.7");
}
public override void BuildFries()
{
happy_meal.Add("BigFries");
}
public override void BuildToy()
{
happy_meal.Add("Two toys");
}
public override HappyMeal GetProduct()
{
return happy_meal;
}
}
Вы видите, что у данного класса есть одна переменная happy_meal, имеющая тип вашего продукта. Ее-то вы и будете использовать во всех переопределенных методах. В каждом из них вы должны добавить информацию о продукте с помощью метода Add, описанного ранее. В методе GetProduct вы просто возвращаете вашу переменную.
С маленьким HappyMeal все делается аналогично:
// Строитель для маленького // HappyMeal
class SmallHappyMealBuilder : HappyMealBuilder
{
private HappyMeal happy_meal = new HappyMeal();
public override void BuildBurger()
{
happy_meal.Add("Hamburger");
}
public override void BuildPepsi()
{
happy_meal.Add("Pepsi 0.3");
}
public override void BuildFries()
{
happy_meal.Add("SmallFries");
}
public override void BuildToy()
{
happy_meal.Add("One toy");
}
public override HappyMeal GetProduct()
{
return happy_meal;
}
}
Пришло время реализовать класс Director, который будет отвечать за конструирование объектов:
// "Этот класс будет // заниматься конструированием"
class Director
{
// Конструирование объекта // по частям
public void Construct(HappyMealBuilder builder)
{
builder.BuildBurger();
builder.BuildPepsi();
builder.BuildFries();
builder.BuildToy();
}
}
Как видно из фрагмента листинга, все действие сосредоточено вокруг метода Construct. В него вы передаете объект HappyMealBuilder, после чего начинаете пошаговую сборку, поочередно вызывая каждый из Build-методов.
Наконец поработайте с созданными вами классами:
public static void Main()
{
// Создаете класс // Director и строителей // для двух наборов // HappyMeal
Director director = new Director();
HappyMealBuilder big_hmbuilder = new BigHappyMealBuilder();
HappyMealBuilder small_hmbuilder = new SmallHappyMealBuilder();
// Конструируете два // продукта
director.Construct(big_hmbuilder);
HappyMeal hm1 = big_hmbuilder.GetProduct();
hm1.Show();
director.Construct(small_hmbuilder);
HappyMeal hm2 = small_hmbuilder.GetProduct();
hm2.Show();
// Ожидаете действия // пользователя
Console.Read();
}
Работа с созданными классами очень проста. Сначала вы создаете объект класса Director, а затем два объекта Builder, которые будут переданы в метод Construct класса Director, чтобы собрать объект HappyMeal. После конструирования очередного HappyMeal вы передаете его в переменную hm1 или hm2 типа HappyMeal и вызываете метод Show, показывающий содержимое набора.
Для того чтобы лучше понять суть данного паттерна, выполните несколько аналогичных примеров. Попрактикуйтесь с более сложными объектами. Подумайте, где бы вы могли применять данный шаблон.