Рассмотрим четыре важных паттерна проектирования, их назначение и практическую реализацию в C++.
Назначение: Гарантирует, что класс имеет только один экземпляр и предоставляет глобальную точку доступа к нему.
Применение:
Реализация в 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();
Проблемы:
Назначение: Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми.
Применение:
Реализация в 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);
Назначение: Определяет скелет алгоритма, позволяя подклассам переопределять некоторые шаги алгоритма, не меняя его структуры.
Применение:
Реализация в 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();
Назначение: Динамически добавляет объекту новые обязанности, предоставляя гибкую альтернативу наследованию.
Применение:
Реализация в 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 | Структурный | Динамическое добавление функциональности |
Все эти паттерны помогают создавать более гибкий, поддерживаемый и расширяемый код, когда применяются в правильном контексте. В современном C++ многие из них могут быть реализованы более идиоматично с использованием возможностей языка (например, std::function для Strategy, лямбды для небольших Decorator'ов).