Приветствую читателей.
В этом посте хотел бы показать две реализации паттерна «Стратегия». Один способ на основе наследования, другой на основе шаблонного класса. Итак приступим.
Сначала разберемся, что же такое паттерн «Стратегия»? К этому обратимся в википедию и вот что она говорит:
Стратегия — поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путем определения соответствующего класса. Шаблон Strategy позволяет менять выбранный алгоритм независимо от объектов-клиентов, которые его используют.
Так выглядит схема паттерна:
Некоторые из преимуществ паттерна:
- Позволяет выбирать алгоритм динамически
- Вызов всех алгоритмов одним стандартным образом
- Упрощает процесс добавления новых стратегий(алгоритмов)
- Избавляет от множественного использования переключателей (if, else)
Мотивы использования:
- Программа должна обеспечивать различные варианты алгоритма или поведения
- Нужно изменять поведение каждого экземпляра класса
- Необходимо изменять поведение объектов на стадии выполнения
Так, думаю разобрались для чего нужен этот паттерн. Теперь разберемся как же его реализовать.
Стратегия может быть просто интерфейсом — набором функций объеденным в структуру. С помощью набора стратегий мы создаем общий алгоритм поведения. Стратегия должна иметь общий интерфейс, т. е. должна иметь одинаковый набор функций с общим назначением и разной реализацией.
Теперь представим, что мы делаем класс сжатия файлов, этот класс должен уметь сжимать 2-мя способами, сжимать в форматах zip и rar.
Определим иерархию классов:
Базовый класс для стратегий.
struct Compression
{
virtual void Compress(const string &file) = 0;
virtual ~Compression() {}
};
Стратегия для сжатия в zip.
struct ZipCompression : Compression
{
void Compress(const string &file)
{
...
}
};
Стратегия для сжатия в rar.
struct RarCompression : Compression
{
void Compress(const string &file)
{
...
}
};
Класс для использования.
class Compressor
{
private:
Compression *ptr;
public:
Compressor(Compression *comp)
: ptr(comp)
{
}
void compress(const string &file)
{
ptr->Compress(file);
}
~Compressor()
{
delete ptr;
}
};
Использование:
Compressor zip(new ZipCompression);
zip.compress("filename");
Compressor rar(new RarCompression);
rar.compress("filename");
Как видим из примера, мы используем одну реализацию класса Compressor, но в конструкторе передали указатели на разные стратегии, таким образом вызывая у класса разное поведение.
Следующая реализация основывается на шаблонах C++.
Шаблонная реализация паттерна
Недостатком предыдущей реализации является виртуальные функции, они понижают производительность, да и в общем класс выглядит как по мне не очень презентабельно.
Следующий способ реализации будет основываться на шаблонах. Как и раньше создадим стратегии-алгоритмы:
Стратегия для сжатия в zip.
struct ZipCompression
{
void Compress(const string &file)
{
...
}
};
Стратегия для сжатия в rar.
struct RarCompression
{
void Compress(const string &file)
{
...
}
};
Теперь добавим шаблонный класс для использования
template <class CompressionPolicy>
class Compressor
{
private:
CompressionPolicy strategy;
public:
void Compress(const string &file)
{
strategy.Compress(file);
}
};
Использование
typedef Compressor<ZipCompression> Zip;
typedef Compressor<RarCompression> Rar;
...
Этот способ выглядит более аккуратно и избавляет от одного лишнего класса, а также повышает производительность за счет отсутствия виртуальных функций. На самом деле это довольно простой пример, можно было бы комбинировать стратегии, добавить дополнительный тип в шаблон и т. д. но думаю основная идея ясна.
Спасибо за внимание.
Автор: Vladislav_Dudnikov