Патерн «Стратегія» на C++

Вітаю читачів.
В цьому пості хотів би показати дві реалізації патерну «Стратегія». Один спосіб на основі успадкування, інший на основі шаблонного класу. Отже приступимо.
Спочатку розберемося, що ж таке патерн «Стратегія»? До цього звернемося до вікіпедії і ось що вона каже:
Стратегія — поведінковий шаблон проектування, призначений для визначення сімейства алгоритмів, інкапсуляції кожного з них і забезпечення їх взаємозамінності. Це дозволяє вибирати алгоритм шляхом визначення відповідного класу. Шаблон 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;
}
};


Використання:
zip Compressor(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;
...


Цей спосіб виглядає більш акуратно і позбавляє від одного зайвого класу, а також підвищує продуктивність за рахунок відсутності віртуальних функцій. Насправді це досить простий приклад, можна було б комбінувати стратегії, додати додатковий тип шаблон і т. д. але думаю основна ідея зрозуміла.
Дякую за увагу.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.