settings.h: объявление // settings.cpp: определение Ничто не мешает создать второй Settings Глобальные объекты из разных .cpp создаются в неопределённом порядке
instance() { if (!instance_) instance_ = new Settings(); return instance_; } // два потока → два объекта // кто вызовет delete? private: Settings() = default; static Settings* instance_; }; Settings* Settings::instance_ = nullptr; Утечка: delete не вызовут Два потока → два объекта
— по его объявлению этого не понять Тесты Один тест выключил сетку — она выключена и в следующем Подмена В тесте не подставить свои настройки: класс один, и он читает настоящий файл
приложение auto settings = std::make_shared<FileSettings>("settings.txt"); Canvas canvas(settings); Canvas preview(settings); // тот же самый объект Объект по-прежнему один, но класс об этом не заботится
Singleton Один на всё приложение Настройки, журнал Scoped Один на документ (сессию, запрос) История отмены Transient Новый при каждом запросе Фигура, команда Так это называют DI-контейнеры: AddSingleton / AddScoped / AddTransient в .NET, Boost.DI и Google Fruit в C++
Версия Мейерса — короткая и потокобезопасная Недостатки Скрытые зависимости Тесты влияют друг на друга Не подменить в тестах Нужен один объект? Создайте его в main() и передайте через интерфейс
void draw() const = 0; }; class Rectangle final : public Shape { public: Rectangle(Point corner1, Point corner2); void draw() const override; }; class Circle final : public Shape { public: Circle(Point center, int radius); void draw() const override; };
// пользователь протянул мышью от from до to void onDrag(Canvas& canvas, Point from, Point to) const { auto shape = createShape(from, to); // фабричный метод canvas.add(std::move(shape)); } private: [[nodiscard]] virtual std::unique_ptr<Shape> createShape(Point from, Point to) const = 0; }; private virtual: наследник переопределяет, но не вызывает
std::unique_ptr<Shape> createShape(Point from, Point to) const override { return std::make_unique<Rectangle>(from, to); // два угла } }; class CircleTool final : public Tool { private: std::unique_ptr<Shape> createShape(Point from, Point to) const override { return std::make_unique<Circle>(from, distance(from, to)); } }; Новый инструмент — новый класс Tool и Editor не меняются
{200, 100}); // прямоугольник tool = std::make_unique<CircleTool>(); tool->onDrag(canvas, {50, 50}, {80, 50}); // круг радиусом 30 Сценарий один — фигуры и способ их построения разные
против центра и радиуса круга Нет Наследники отличаются только типом фигуры — класс на каждую фигуру будет лишним Для второго случая есть шаблонная фабрика
Tool { private: std::unique_ptr<Shape> createShape(Point from, Point to) const override { return std::make_unique<TShape>(from, to); } }; Один шаблон вместо класса на каждую фигуру Тип фигуры — параметр шаблона
20}); tool = std::make_unique<SimpleTool<Ellipse>>(); tool->onDrag(canvas, {0, 0}, {40, 20}); Подходит фигурам с конструктором (from, to). У круга своя логика — для него остаётся CircleTool
итератор Но наследования и virtual здесь нет — шаблонный код просто вызывает c.begin() Это статический («утиный») полиморфизм, а не классический Factory Method
инструмент — новый класс, Tool не меняется Недостатки Класс на каждый вариант — если отличие только в типе, лучше шаблон или функция Логика создания разбросана по наследникам
{ add(std::make_unique<DarkButton>()); add(std::make_unique<DarkPanel>()); } else { add(std::make_unique<LightButton>()); add(std::make_unique<DarkPanel>()); // ошибка: не та тема! } } Проверка темы — в каждом окне Легко ошибиться: тёмная панель в светлой теме Новая тема → правим все окна
[[nodiscard]] virtual std::unique_ptr<Button> createButton() const = 0; [[nodiscard]] virtual std::unique_ptr<Panel> createPanel() const = 0; }; Один метод на каждый вид элемента
std::unique_ptr<Button> createButton() const override { return std::make_unique<DarkButton>(); } std::unique_ptr<Panel> createPanel() const override { return std::make_unique<DarkPanel>(); } }; LightTheme — так же, но со светлыми элементами
if (dark) return std::make_unique<DarkTheme>(); return std::make_unique<LightTheme>(); } auto theme = makeTheme(darkMode); Window window(*theme); window.draw(); // тема выбирается в одном месте // окно не знает, какая тема // dark panel, dark button
Buffer Texture Shader OpenGL GLDevice GLBuffer GLTexture GLShader Vulkan VulkanDevice VulkanBuffer VulkanTexture VulkanShader Движок прячет разные API за общими интерфейсами. Остальной код работает только с Buffer, Texture, Shader — и не знает, какой API под ними Так устроены, например, Diligent Engine (IRenderDevice) и RHI в Unreal Engine
Один объект Набор объектов, которые подходят друг другу Как Подкласс переопределяет метод Клиенту передают объект-фабрику В редакторе CircleTool → Circle DarkTheme → тёмные кнопка и панель Новый вариант Новый подкласс Новая фабрика
const; private: TextStyle() = default; // для примера // только через Builder std::string font_ = "Arial"; int size_ = 12; bool bold_ = false; bool italic_ = false; bool underline_ = false; std::uint32_t color_ = 0x000000; Align align_ = Align::Left; }; Создать можно только через Builder У всех полей есть значения по умолчанию
.align(Align::Center) .build(); TextStyle body = TextStyle::Builder().size(14).build(); Каждый параметр подписан Только нужные шаги // остальное по умолчанию build() проверяет стиль
у *selected_? if (auto r = dynamic_cast<const Rectangle*>(selected_)) canvas_.add(std::make_unique<Rectangle>(*r)); else if (auto c = dynamic_cast<const Circle*>(selected_)) canvas_.add(std::make_unique<Circle>(*c)); // ...и так для каждой фигуры } Перебор всех типов Новая фигура → правим Editor Забыли тип → фигура не копируется
selected_->clone(); // настоящий тип знать не нужно Point p = selected_->position(); copy->moveTo({p.x + 10, p.y + 10}); // чуть сдвинем canvas_.add(std::move(copy)); } Новая фигура — Editor не меняется
холсте Пользователь собрал из фигур кнопку и сохранил её как компонент — как в Figma Новый «вид» объекта появился во время работы программы — класса для него нет
button->add(std::make_unique<Rectangle>(120, 40)); button->add(std::make_unique<Text>("OK")); ComponentLibrary library; library.save("Кнопка", std::move(button)); auto b1 = library.insert("Кнопка"); auto b2 = library.insert("Кнопка"); // независимые копии Компонент «Кнопка» — это данные, а не класс в программе
тип Derived }; class Triangle : public Base<Triangle> {}; Curiously Recurring Template Pattern — «странно повторяющийся шаблон» Класс наследуется от шаблона, параметризованного им самим База ещё при компиляции знает тип наследника
объектов — без новых классов Недостатки clone() нужен в каждом классе (CRTP помогает) Глубокое копирование сложных структур, например с циклическими ссылками
Singleton или DI Settings Подкласс решает, что создать? Factory Method CircleTool Нужен набор подходящих друг другу объектов? Abstract Factory DarkTheme Много параметров или шагов? Builder TextStyle::Builder Нужна копия, а тип неизвестен? Prototype Shape::clone()