В этой серии мы рассмотрим творческие шаблоны проектирования:

  1. Синглтон
  2. Фабричный метод
  3. Абстрактная Фабрика
  4. "Опытный образец"
  5. Конструктор

🧐 Что это?

Конструктор позволяет постепенно создавать сложные предметы. Шаблон позволяет создавать множество типов и представлений объекта, используя один и тот же код создания.
Кроме того, он используется для различения фактической конструкции объекта и его представления. Это упрощает создание сложных элементов по частям, а также упрощает изменение представления объекта без изменения его конструкции.

Существует два типа этого шаблона проектирования:

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

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

🥺 Почему?

Допустим, у нас есть игра, в которой игрок может создать свое собственное оружие, выбирая различные части, такие как рукоять, клинок и чары. Каждая деталь имеет свой набор свойств, которые необходимо задать для создания полнофункционального оружия, и возможных комбинаций деталей может быть множество. Здесь может пригодиться шаблон Builder.

Мы можем создать класс Weapon с такими свойствами, как name, damage, durability и enchantment, а затем создать класс WeaponBuilder, который реализует интерфейс Builder для создания объектов Weapon. Класс WeaponBuilder может иметь методы для установки различных свойств оружия и может гарантировать, что свойства будут установлены в правильном порядке.

Посмотрим, что будет без Builder:

class Weapon {
  constructor(
    public name: string,
    public damage: number,
    public durability: number,
    public enchantment: string) {}
}

// Usage example
const weapon = new Weapon("Sword of Fire", 50, 100, "Fire");
console.log(weapon);

Здесь мы создаем новый объект Weapon непосредственно в конструкторе класса Weapon. Хотя это прекрасно работает для простого примера, такого как этот, он может быстро стать громоздким для более сложных объектов со многими свойствами.

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

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

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

🥳 Состав:

  1. В интерфейсе Builder объявлены стандартные для всех сборщиков процедуры создания продукта.
  2. Concrete Builders реализует процесс строительства разными способами. Продукты бетонных подрядчиков не могли придерживаться стандартного интерфейса.
  3. (Необязательно) Класс Director определяет порядок вызова шагов построения, чтобы вы могли создавать и повторно использовать определенные конфигурации продуктов.
    В шаблоне проектирования "Строитель" директор является необязательным компонентом, который управляет порядком, в котором собираются строительные блоки сложного объекта. Директор выступает посредником между клиентом и Builder, и он определяет шаги, связанные с созданием конечного объекта.
    Режиссер не всегда необходим, но может быть полезен, когда есть сложный объект с множество строительных блоков, которые необходимо собрать в определенном порядке. При использовании Директора клиентскому коду не нужно знать подробности того, как строится объект, он может просто предоставить необходимые входные данные Директору и позволить ему справиться с созданием объекта.
    Директор работает. с интерфейсом Builder, который определяет методы построения каждой части объекта. Интерфейс Builder предоставляет директору возможность работать с любым объектом Builder, который реализует интерфейс, обеспечивая большую гибкость при создании объектов.
interface Builder {
  buildPartA(): void;
  buildPartB(): void;
  buildPartC(): void;
}

class ConcreteBuilder implements Builder {
  buildPartA() {
    console.log('Building part A');
  }
  buildPartB() {
    console.log('Building part B');
  }
  buildPartC() {
    console.log('Building part C');
  }
}
class Director {
  private builder: Builder;
  constructor(builder: Builder) {
    this.builder = builder;
  }
  construct() {
    this.builder.buildPartA();
    this.builder.buildPartB();
    this.builder.buildPartC();
  }
}
// Usage example
const builder = new ConcreteBuilder();
const director = new Director(builder);
director.construct();

Классический и свободный

😎 Давайте посмотрим пример:

Мы создадим оружие, о котором говорили ранее:

class Weapon {
  constructor(public name: string, public damage: number, public durability: number, public enchantment: string) {}
}

interface Builder {
  setName(name: string): void;
  setDamage(damage: number): void;
  setDurability(durability: number): void;
  setEnchantment(enchantment: string): void;
  build(): Weapon;
}
class WeaponBuilder implements Builder {
  private name: string = "";
  private damage: number = 0;
  private durability: number = 0;
  private enchantment: string = "";
  setName(name: string) {
    this.name = name;
  }
  setDamage(damage: number) {
    this.damage = damage;
  }
  setDurability(durability: number) {
    this.durability = durability;
  }
  setEnchantment(enchantment: string) {
    this.enchantment = enchantment;
  }
  build() {
    return new Weapon(this.name, this.damage, this.durability, this.enchantment);
  }
}
// Usage example
const builder = new WeaponBuilder();
builder.setName("Sword of Fire");
builder.setDamage(50);
builder.setDurability(100);
builder.setEnchantment("Fire");
const weapon = builder.build();
console.log(weapon);

Теперь у нас есть класс Weapon, который представляет пользовательское оружие в игре, и класс WeaponBuilder, который реализует интерфейс Builder и предоставляет необходимые методы для создания объектов Weapon. Затем мы создаем новый WeaponBuilder и используем его методы для установки различных свойств оружия. Наконец, мы вызываем метод build() для WeaponBuilder, чтобы создать новый объект Weapon с указанными свойствами и вывести его на консоль.

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

Мы можем пойти еще дальше, представив Директора:

class Weapon {
  constructor(public name: string, public damage: number, public durability: number, public enchantment: string) {}
}

interface Builder {
  setName(name: string): void;
  setDamage(damage: number): void;
  setDurability(durability: number): void;
  setEnchantment(enchantment: string): void;
  getResult(): Weapon;
}
class BasicSwordBuilder implements Builder {
  private weapon: Weapon;
  constructor() {
    this.weapon = new Weapon("Basic Sword", 10, 50, "");
  }
  setName(name: string) {
    this.weapon.name = name;
  }
  setDamage(damage: number) {
    this.weapon.damage = damage;
  }
  setDurability(durability: number) {
    this.weapon.durability = durability;
  }
  setEnchantment(enchantment: string) {
    this.weapon.enchantment = enchantment;
  }
  getResult() {
    return this.weapon;
  }
}
class FireSwordBuilder implements Builder {
  private weapon: Weapon;
  constructor() {
    this.weapon = new Weapon("Fire Sword", 20, 40, "Fire");
  }
  setName(name: string) {
    this.weapon.name = name;
  }
  setDamage(damage: number) {
    this.weapon.damage = damage;
  }
  setDurability(durability: number) {
    this.weapon.durability = durability;
  }
  setEnchantment(enchantment: string) {
    this.weapon.enchantment = enchantment;
  }
  getResult() {
    return this.weapon;
  }
}
class WeaponDirector {
  constructor(private builder: Builder) {}
  createWeapon() {
    this.builder.setName("Custom Weapon");
    this.builder.setDamage(30);
    this.builder.setDurability(60);
    this.builder.setEnchantment("Ice");
    return this.builder.getResult();
  }
}
// Usage example
const basicSwordBuilder = new BasicSwordBuilder();
const fireSwordBuilder = new FireSwordBuilder();
const director = new WeaponDirector(basicSwordBuilder);
const basicSword = director.createWeapon();
director.builder = fireSwordBuilder;
const fireSword = director.createWeapon();
console.log(basicSword);
console.log(fireSword);

Здесь у нас есть два разных класса WeaponBuilder (BasicSwordBuilder и FireSwordBuilder), каждый из которых создает объекты Weapon с разными свойствами по умолчанию. У нас также есть класс WeaponDirector, который принимает объект Builder в качестве параметра и использует его для создания пользовательских объектов Weapon с определенными свойствами.

Затем мы создаем экземпляры классов BasicSwordBuilder и FireSwordBuilder и используем WeaponDirector для создания пользовательского оружия с BasicSwordBuilder и FireSwordBuilder.

🤓 Как использовать конструктор в TypeScript

Шаг 1: Определите класс оружия

class Weapon {
  constructor(
     public name: string,
     public damage: number,
     public durability: number,
     public enchantment: string) {}
}

Этот класс представляет объекты, которые мы хотим создать с помощью шаблона Builder. Он имеет четыре свойства: name, damage, durability и enchantment.

Шаг 2: Определите интерфейс Builder

interface Builder {
  setName(name: string): void;
  setDamage(damage: number): void;
  setDurability(durability: number): void;
  setEnchantment(enchantment: string): void;
  getResult(): Weapon;
}

Этот интерфейс определяет методы, которые должны реализовать все классы Builder для создания объектов Weapon. Каждый метод соответствует свойству Weapon, а метод getResult возвращает конечный объект Weapon.

Шаг 3: Реализуйте класс BasicSwordBuilder

class BasicSwordBuilder implements Builder {
  private weapon: Weapon;

constructor() {
      this.weapon = new Weapon("Basic Sword", 10, 50, "");
  }
  setName(name: string) {
    this.weapon.name = name;
  }
  setDamage(damage: number) {
    this.weapon.damage = damage;
  }
  setDurability(durability: number) {
    this.weapon.durability = durability;
  }
  setEnchantment(enchantment: string) {
    this.weapon.enchantment = enchantment;
  }
  getResult() {
    return this.weapon;
  }
}

Этот класс реализует интерфейс Builder для создания объектов Weapon со свойствами по умолчанию для обычного меча.

Шаг 4: Реализуйте класс FireSwordBuilder

class FireSwordBuilder implements Builder {
  private weapon: Weapon;

constructor() {
      this.weapon = new Weapon("Fire Sword", 20, 40, "Fire");
    }
    setName(name: string) {
      this.weapon.name = name;
    }
    setDamage(damage: number) {
      this.weapon.damage = damage;
    }
    setDurability(durability: number) {
      this.weapon.durability = durability;
    }
    setEnchantment(enchantment: string) {
      this.weapon.enchantment = enchantment;
    }
    getResult() {
      return this.weapon;
    }
}

Этот класс реализует интерфейс Builder для создания объектов Weapon со свойствами по умолчанию для огненного меча.

Шаг 5: Реализуйте класс WeaponDirector

class WeaponDirector {
  constructor(private builder: Builder) {}
  
  createWeapon() {
    this.builder.setName("Custom Weapon");
    this.builder.setDamage(30);
    this.builder.setDurability(60);
    this.builder.setEnchantment("Ice");
    return this.builder.getResult();
  }
}

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

Шаг 6: Используйте шаблон Builder для создания нестандартного оружия

const basicSwordBuilder = new BasicSwordBuilder();
const fireSwordBuilder = new FireSwordBuilder();
const director = new WeaponDirector(basicSwordBuilder);

// Create a basic sword using the director
const basicSword = director.createWeapon();
console.log(basicSword);
// Create a fire sword using the director
director.builder = fireSwordBuilder;
const fireSword = director.createWeapon();
console.log(fireSword);
// Create a custom sword without using the director
const customSword = new Weapon("Custom Sword", 40, 80, "Lightning");
console.log(customSword);

🥰 Когда использовать?

  1. Чтобы избавиться от массивного конструктора, используйте шаблон Builder.
  2. Если вы хотите, чтобы ваш код мог генерировать различные представления определенного продукта, используйте шаблон Builder (например, меч и магический 3000 меч света и права).

✅ Преимущества

  1. Он упрощает создание сложных объектов, разбивая его на более мелкие и простые этапы.
  2. Он отделяет конструкцию объекта от его представления, упрощая изменение представления объекта, не влияя на его конструкцию.
  3. Это упрощает создание различных вариаций одного и того же объекта с помощью разных компоновщиков.
  4. Это делает код более читабельным и удобным для сопровождения, отделяя логику построения от остального кода.

❌ Недостатки

  1. Общая сложность кода увеличивается, поскольку шаблон требует создания нескольких новых классов.

😊 Заключение

Шаблон построителя — это мощный шаблон проектирования, который используется для создания сложных объектов. Отделяя конструкцию объекта от его представления, упрощается пошаговое создание сложных объектов, а также упрощается изменение представления объекта, не влияя на его конструкцию.

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

Кроме того, важно отметить, что шаблон построителя — не единственный шаблон проектирования, который можно использовать для создания сложных объектов. Другие шаблоны проектирования, такие как шаблон фабрики и абстрактный шаблон фабрики, также могут использоваться для создания сложных объектов. Выбор используемого шаблона проектирования зависит от конкретных требований проекта.

В целом, шаблон построителя является полезным инструментом в вашем наборе инструментов. Он упрощает создание сложных объектов, делает код более читабельным и удобным для сопровождения, а также позволяет легко создавать различные варианты одного и того же объекта. Поняв преимущества и ограничения шаблона построителя, вы сможете эффективно использовать его в своих проектах.

Дополнительные пояснения и примеры кода можно найти в моем репозитории:



Надеюсь, вам понравилась эта статья! ❤️