Патерны Singleton, Strategy, Template-Method, Decorator?cplus-39

Рассмотрим четыре важных паттерна проектирования, их назначение и практическую реализацию в C++.

1. Singleton

Назначение: Гарантирует, что класс имеет только один экземпляр и предоставляет глобальную точку доступа к нему.

Применение:

  • Логгеры
  • Доступ к конфигурации
  • Менеджеры ресурсов

Реализация в C++:

class Singleton {
    static Singleton* instance;
    
    // Закрываем конструкторы
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    void doWork() { /* ... */ }
};

Singleton* Singleton::instance = nullptr;

// Использование:
Singleton::getInstance()->doWork();

Проблемы:

  • Потокобезопасность (в базовой реализации)
  • Сложность тестирования
  • Глобальное состояние

2. Strategy

Назначение: Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми.

Применение:

  • Различные алгоритмы сортировки
  • Варианты оплаты
  • Политики кэширования

Реализация в C++:

class SortStrategy {
public:
    virtual void sort(std::vector<int>& data) = 0;
};

class QuickSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "Quick sort implementation\n";
        // Реализация быстрой сортировки
    }
};

class MergeSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "Merge sort implementation\n";
        // Реализация сортировки слиянием
    }
};

class Sorter {
    std::unique_ptr<SortStrategy> strategy;
public:
    void setStrategy(std::unique_ptr<SortStrategy> s) {
        strategy = std::move(s);
    }
    void execute(std::vector<int>& data) {
        strategy->sort(data);
    }
};

// Использование:
Sorter sorter;
sorter.setStrategy(std::make_unique<QuickSort>());
std::vector<int> data = {5, 2, 7, 1};
sorter.execute(data);

3. Template Method

Назначение: Определяет скелет алгоритма, позволяя подклассам переопределять некоторые шаги алгоритма, не меняя его структуры.

Применение:

  • Алгоритмы с вариативными шагами
  • Фреймворки
  • Обработка документов

Реализация в C++:

class DataProcessor {
public:
    // Шаблонный метод
    void process() {
        open();
        readData();
        transform();
        close();
    }
    
    virtual ```DataProcessor() = default;

protected:
    virtual void open() { /* Базовая реализация */ }
    virtual void readData() = 0; // Абстрактный метод
    virtual void transform() { /* Базовая реализация */ }
    virtual void close() { /* Базовая реализация */ }
};

class CSVProcessor : public DataProcessor {
protected:
    void readData() override {
        std::cout << "Reading CSV data\n";
    }
    void transform() override {
        std::cout << "Transforming CSV data\n";
    }
};

// Использование:
DataProcessor* processor = new CSVProcessor();
processor->process();

4. Decorator

Назначение: Динамически добавляет объекту новые обязанности, предоставляя гибкую альтернативу наследованию.

Применение:

  • Добавление функциональности к UI компонентам
  • Логирование, кэширование, сжатие
  • Потоковые обработчики

Реализация в C++:

class Coffee {
public:
    virtual ```Coffee() = default;
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
};

class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override {
        return "Simple Coffee";
    }
    double cost() const override {
        return 1.0;
    }
};

class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee;
public:
    CoffeeDecorator(Coffee* c) : coffee(c) {}
    virtual ```CoffeeDecorator() { delete coffee; }
};

class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(Coffee* c) : CoffeeDecorator(c) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double cost() const override {
        return coffee->cost() + 0.5;
    }
};

// Использование:
Coffee* coffee = new MilkDecorator(new SimpleCoffee());
std::cout << coffee->getDescription() << ": $" << coffee->cost();
delete coffee;

Сравнение паттернов

ПаттернТипОсновная цель
SingletonПорождающийЕдинственный экземпляр класса
StrategyПоведенческийВзаимозаменяемые алгоритмы
Template MethodПоведенческийСкелет алгоритма с изменяемыми шагами
DecoratorСтруктурныйДинамическое добавление функциональности

Резюмируем

  1. Singleton - для единственного экземпляра класса, но требует осторожного использования
  2. Strategy - когда нужно выбирать алгоритм во время выполнения
  3. Template Method - для алгоритмов с вариативными шагами
  4. Decorator - для динамического добавления функциональности без наследования

Все эти паттерны помогают создавать более гибкий, поддерживаемый и расширяемый код, когда применяются в правильном контексте. В современном C++ многие из них могут быть реализованы более идиоматично с использованием возможностей языка (например, std::function для Strategy, лямбды для небольших Decorator'ов).