Menu
Coddy logo textTech

Паттерн Builder

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 93 из 104.

Паттерн Строитель (Builder) отделяет конструирование сложного объекта от его представления, позволяя создавать различные конфигурации шаг за шагом. Это особенно полезно, когда объект имеет множество необязательных параметров или требует определенной последовательности конструирования.

Вместо конструктора с множеством параметров (что становится запутанным), Строитель (Builder) предоставляет четкие именованные методы для каждого варианта конфигурации:

#include <iostream>
#include <string>

class Pizza {
public:
    std::string dough;
    std::string sauce;
    std::string topping;
    bool cheese;
    
    void describe() const {
        std::cout << dough << " dough, " << sauce << " sauce, "
                  << topping << (cheese ? ", with cheese" : "") << "\n";
    }
};

class PizzaBuilder {
private:
    Pizza pizza;
    
public:
    PizzaBuilder& setDough(const std::string& d) {
        pizza.dough = d;
        return *this;
    }
    
    PizzaBuilder& setSauce(const std::string& s) {
        pizza.sauce = s;
        return *this;
    }
    
    PizzaBuilder& setTopping(const std::string& t) {
        pizza.topping = t;
        return *this;
    }
    
    PizzaBuilder& addCheese() {
        pizza.cheese = true;
        return *this;
    }
    
    Pizza build() { return pizza; }
};

int main() {
    Pizza margherita = PizzaBuilder()
        .setDough("thin")
        .setSauce("tomato")
        .setTopping("basil")
        .addCheese()
        .build();
    
    margherita.describe();
}

Каждый метод-сеттер возвращает ссылку на строитель (return *this), позволяя использовать цепочку вызовов методов для реализации текучего интерфейса. Метод build() завершает создание и возвращает сконструированный объект.

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

challenge icon

Задание

Легко

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

Вы организуете свой код в трех файлах:

  • Computer.h: Определите класс продукта, который представляет собой полностью сконфигурированный компьютер.

    Ваш класс Computer должен хранить эти компоненты в качестве приватных членов: cpu (string), ram (string), storage (string), gpu (string) и hasWifi (boolean). Инициализируйте hasWifi значением false, а строки по умолчанию сделайте пустыми.

    Добавьте публичный метод showSpecs(), который отображает конфигурацию компьютера. Для каждого установленного компонента (непустая строка) выведите его на отдельной строке. Наличие Wi-Fi должно выводиться только в том случае, если оно включено.

  • ComputerBuilder.h: Создайте класс строителя, который конструирует компьютеры с использованием текучего интерфейса (fluent interface).

    Ваш класс ComputerBuilder должен иметь приватный член Computer, который он собирает. Реализуйте следующие методы, каждый из которых возвращает ссылку на строителя для обеспечения цепочки вызовов (method chaining):

    • setCPU(const std::string& cpu)
    • setRAM(const std::string& ram)
    • setStorage(const std::string& storage)
    • setGPU(const std::string& gpu)
    • addWifi() — включает возможность Wi-Fi

    Также реализуйте метод build(), который возвращает готовый объект Computer и сбрасывает состояние строителя для возможного повторного использования.

  • main.cpp: Объедините все вместе, чтобы собрать кастомный компьютер.

    Прочитайте четыре входных значения:

    1. Модель процессора (string)
    2. Спецификация оперативной памяти (string)
    3. Спецификация накопителя (string)
    4. Нужно ли включать Wi-Fi: yes или no

    Используйте ComputerBuilder для сборки компьютера с указанными CPU, RAM и накопителем. Если на вход для Wi-Fi подано yes, также вызовите addWifi(). Обратите внимание, что GPU в этой конфигурации не указывается — строитель должен корректно обрабатывать опциональные компоненты.

    После сборки вызовите showSpecs() для полученного компьютера, чтобы отобразить его конфигурацию.

Метод showSpecs() должен выводить данные в следующем формате (показывая только те компоненты, которые были установлены):

CPU: [value]
RAM: [value]
Storage: [value]
GPU: [value]
Wifi: Enabled

Например, при входных данных Intel i7-12700K, 32GB DDR5, 1TB NVMe SSD и yes:

CPU: Intel i7-12700K
RAM: 32GB DDR5
Storage: 1TB NVMe SSD
Wifi: Enabled

При входных данных AMD Ryzen 5 5600X, 16GB DDR4, 512GB SSD и no:

CPU: AMD Ryzen 5 5600X
RAM: 16GB DDR4
Storage: 512GB SSD

Обратите внимание, как паттерн Builder делает процесс сборки читаемым и гибким — вы можете легко добавлять или пропускать компоненты, а цепочка методов создает понятный, самодокументированный код. Строка с GPU не появляется, потому что мы её не устанавливали, что демонстрирует, как строители естественным образом обрабатывают опциональные компоненты.

Шпаргалка

Паттерн Строитель (Builder) отделяет конструирование сложного объекта от его представления, позволяя выполнять пошаговую настройку. Он полезен, когда объект имеет множество необязательных параметров или требует определенной последовательности создания.

Вместо конструкторов с большим количеством параметров, Строитель предоставляет понятные именованные методы для каждого варианта конфигурации:

class Pizza {
public:
    std::string dough;
    std::string sauce;
    std::string topping;
    bool cheese;
};

class PizzaBuilder {
private:
    Pizza pizza;
    
public:
    PizzaBuilder& setDough(const std::string& d) {
        pizza.dough = d;
        return *this;  // Enable method chaining
    }
    
    PizzaBuilder& setSauce(const std::string& s) {
        pizza.sauce = s;
        return *this;
    }
    
    PizzaBuilder& setTopping(const std::string& t) {
        pizza.topping = t;
        return *this;
    }
    
    PizzaBuilder& addCheese() {
        pizza.cheese = true;
        return *this;
    }
    
    Pizza build() { return pizza; }
};

// Usage with method chaining
Pizza margherita = PizzaBuilder()
    .setDough("thin")
    .setSauce("tomato")
    .setTopping("basil")
    .addCheese()
    .build();

Каждый метод-сеттер возвращает return *this, что позволяет использовать цепочку вызовов методов (method chaining) для создания текучего интерфейса. Метод build() завершает процесс и возвращает сконструированный объект.

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

Попробуйте сами

#include <iostream>
#include <string>
#include "ComputerBuilder.h"

using namespace std;

int main() {
    // Чтение входных данных
    string cpu, ram, storage, wifiChoice;
    getline(cin, cpu);
    getline(cin, ram);
    getline(cin, storage);
    getline(cin, wifiChoice);

    // TODO: Создать экземпляр ComputerBuilder
    
    // TODO: Использовать цепочку методов для установки cpu, ram и storage
    
    // TODO: Если wifiChoice равен "yes", также вызвать addWifi()
    
    // TODO: Вызвать build(), чтобы получить объект Computer
    
    // TODO: Вызвать showSpecs() для собранного компьютера, чтобы отобразить конфигурацию

    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование