Стратегія (шаблон проєктування)
Стратегія (англ. Strategy) — шаблон проєктування, належить до класу шаблонів поведінки. Відомий ще під іншою назвою — «Policy». Його суть полягає у тому, щоб створити декілька схем поведінки для одного об'єкту та винести в окремий клас. Шаблон Стратегія (Strategy) дозволяє міняти вибраний алгоритм незалежно від об'єктів-клієнтів, які його використовують.
Основні характеристики
Завдання
За типом клієнта (або за типом оброблюваних даних) вибрати відповідний алгоритм, який слід застосувати. Якщо використовується правило, яке не піддається змінам, немає необхідності звертатися до шаблону «стратегія».
Структура
Мотиви
- Програма повинна забезпечувати різні варіанти алгоритму або поведінки
- Потрібно змінювати поведінку кожного екземпляра класу
- Необхідно змінювати поведінку об'єктів на стадії виконання
- Введення інтерфейсу дозволяє класам-клієнтам нічого не знати про класи, що реалізують цей інтерфейс і інкапсулюють в собі конкретні алгоритми
Спосіб вирішення
Відділення процедури вибору алгоритму від його реалізації. Це дозволяє зробити вибір на підставі контексту.
Учасники
- Клас
Strategy
визначає, як будуть використовуватися різні алгоритми. - Конкретні класи
ConcreteStrategy
реалізують ці різні алгоритми. - Клас
Context
використовує конкретні класиConcreteStrategy
за допомогою посилання на конкретний тип абстрактного класуStrategy
. КласиStrategy
іContext
взаємодіють з метою реалізації обраного алгоритму (в деяких випадках класуStrategy
потрібно надсилати запити класуContext
). КласContext
пересилає класуStrategy
запит, що надійшов від його класу-клієнта.
Наслідки
- Шаблон Strategy визначає сімейство алгоритмів.
- Це дозволяє відмовитися від використання перемикачів і / або умовних операторів.
- Виклик всіх алгоритмів повинен здійснюватися стандартним чином (всі вони повинні мати однаковий інтерфейс).
Реалізація
Клас, який використовує алгоритм (Context
), включає абстрактний клас (Strategy
), що володіє абстрактним методом, визначальним спосіб виклику алгоритму. Кожен похідний клас реалізує один необхідний варіант алгоритму.
Використання
Архітектура Microsoft WDF заснована на цьому патерні. У кожного об'єкта «драйвер» і «пристрій» є незмінна частина, вшита в систему, в якій реєструється змінна частина (стратегія), написана в конкретній реалізації. Змінна частина може бути і зовсім порожньою, що означає, що драйвер нічого не робить, але при цьому здатний брати участь у PnP і управлінні живленням.
Бібліотека ATL містить у собі набір класів threading model, які є стратегіями (різними реалізаціями Lock / Unlock, які потім використовуються основними класами системи). При цьому в цих стратегіях використовується статичний поліморфізм через параметр шаблону, а не динамічний поліморфізм через віртуальні методи.
Призначення шаблону проєктування Стратегія
Існують системи, поведінка яких визначається відповідно до певного роду алгоритмів. Всі вони подібні між собою: призначені для вирішення спільних задач, мають однаковий інтерфейс для користування, але відрізняються тільки «поведінкою», тобто реалізацією. Користувач, налаштувавши програму на потрібний алгоритм — отримує потрібний результат.
- Приклад. Є програма(інтерфейс) через яку обраховується ціна на товар для покупців у яких є знижка та ціна за сезонною знижкою — обираємо необхідний алгоритм. Об'єктно-орієнтованиий дизайн такої програми будується на ідеї використання поліморфізму. Результатом є набір «класів-родичів» — у яких єдиний інтерфейс та різна реалізація алгоритмів.
- Недоліками такого алгоритму є те, що реалізація жорстко прив'язана до підкласу, що ускладнює внесення змін.
- Вирішенням даної проблеми є використання патерну Стратегія (Strategy).
Переваги
- Можливість позбутися умовних операторів.
- Клієнт може вибирати найбільш влучну стратегію залежно від вимог щодо швидкодії і пам'яті.
Недоліки
- Збільшення кількості об'єктів.
- Клієнт має знати особливості реалізацій стратегій для вибору найбільш вдалої.
Зв'язок з іншими патернами
- Стратегія змінює реалізацію, декоратор — доповнює
- В стратегії користувач знає про класи стратегій і міняє їх самостійно, в стані різноманітні стани приховані від користувача, а за їх заміну відповідає сам клас
- Міст — це структурний патерн. Його компоненти зазвичай встановлюються раз і не змінюються під час виконання програми. Використовують для розділення абстракції та реалізації. Стратегія — це шаблон поведінки. Використовують коли коли алгоритми можуть замінювати один одного під час виконання програми.
- Шаблонний метод задає кроки алгоритму, які реалізовують підкласи. Стратегія задає алгоритм який можна виконати декількома способами, до того ж вибрати ці способи на етапі виконання програми
Приклади
Приклад на Java
// Клас реалізує конкретну стратегію, повинен успадковувати цей інтерфейс
// Клас контексту використовує цей інтерфейс для виклику конкретної стратегії
interface Strategy {
int execute(int a, int b);
}
// Реалізуємо алгоритм з використанням інтерфейсу стратегії
class ConcreteStrategyAdd implements Strategy {
public int execute(int a, int b) {
System.out.println("Called ConcreteStrategyAdd's execute()");
return a + b; // Do an addition with a and b
}
}
class ConcreteStrategySubtract implements Strategy {
public int execute(int a, int b) {
System.out.println("Called ConcreteStrategySubtract's execute()");
return a - b; // Do a subtraction with a and b
}
}
class ConcreteStrategyMultiply implements Strategy {
public int execute(int a, int b) {
System.out.println("Called ConcreteStrategyMultiply's execute()");
return a * b; // Do a multiplication with a and b
}
}
// Клас контексту використовує інтерфейс стратегії
class Context {
private Strategy strategy;
// Constructor
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
}
// Тестовий додаток
class StrategyExample {
public static void main(String[] args) {
Context context;
context = new Context(new ConcreteStrategyAdd());
int resultA = context.executeStrategy(3,4);
context = new Context(new ConcreteStrategySubtract());
int resultB = context.executeStrategy(3,4);
context = new Context(new ConcreteStrategyMultiply());
int resultC = context.executeStrategy(3,4);
}
}
Приклад на C++
#include <iostream>
using namespace std;
// загальний спосіб вирішення проблеми
struct NameStrategy
{
virtual void greet() = 0;
};
// Конкретні рецепти вирішення
struct SayHi : public NameStrategy
{
void greet()
{
cout << " Hi!How is it going ? " << endl;
}
};
struct Ignore : public NameStrategy
{
void greet()
{
cout << " (Pretend I do not see you)" << endl;
}
};
struct Admission : public NameStrategy
{
void greet()
{
cout << " I am sorry.I forgot your name." << endl;
}
};
// Контекст керує стратегією («Посередник»)
// «Стан» - якщо можлива зміна стратегії за життя контексту
class Context
{
private:
NameStrategy& strategy;
public:
Context(NameStrategy& strat) : strategy(strat) {}
void greet() { strategy.greet(); } // постійний код
};
void main()
{
SayHi sayhi;
Ignore ignore;
Admission admission;
Context c1(sayhi), c2(ignore), c3(admission);
c1.greet();
c2.greet();
c3.greet();
}
class Strategy
{
protected:
Strategy(void){}
public:
virtual ~Strategy(void){}
virtual void use(void) = 0;
};
class Strategy_1: public Strategy
{
public:
Strategy_1(){}
~Strategy_1(){}
void use(void){ cout << "Strategy_1" << endl; };
};
class Strategy_2: public Strategy
{
public:
Strategy_2(){}
~Strategy_2(){}
void use(void){ cout << "Strategy_2" << endl; };
};
class Strategy_3: public Strategy
{
public:
Strategy_3(){}
~Strategy_3(){}
void use(void){ cout << "Strategy_3" << endl; };
};
class Context
{
protected:
Strategy* operation;
public:
Context(void){}
~Context(void){}
virtual void UseStrategy(void) = 0;
virtual void SetStrategy(Strategy* v) = 0;
};
class Client: public Context
{
public:
Client(void){}
~Client(void){}
void UseStrategy(void)
{
operation->use();
}
void SetStrategy(Strategy* o)
{
operation = o;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Client customClient;
Strategy_1 str1;
Strategy_2 str2;
Strategy_3 str3;
customClient.SetStrategy(&str1);
customClient.UseStrategy();
customClient.SetStrategy(&str2);
customClient.UseStrategy();
customClient.SetStrategy(&str3);
customClient.UseStrategy();
return 0;
}
Приклад на C #
using System;
namespace DesignPatterns.Behavioral.Strategy
{
/// <summary>
/// Інтерфейс «Стратегія» визначає функціональність (в даному прикладі це метод
/// <see Cref="Algorithm"> Algorithm </see>), яка повинна бути реалізована
/// конкретними класами стратегій. Іншими словами, метод інтерфейсу визначає
/// вирішення якоїсь задачі, а його реалізації в конкретних класах стратегій визначають,
/// яким шляхом ця задача буде вирішена.
/// </ Summary>
public interface IStrategy
{
void Algorithm();
}
/// <summary>
/// Перша конкретна реалізація-стратегія.
/// </summary>
public class ConcreteStrategy1 : IStrategy
{
public void Algorithm()
{
Console.WriteLine("Виконується алгоритм стратегії 1.");
}
}
/// <summary>
/// Друга конкретна реалізація-стратегія.
/// Реалізацій може бути скільки завгодно багато.
/// </Summary>
public class ConcreteStrategy2 : IStrategy
{
public void Algorithm()
{
Console.WriteLine("Виконується алгоритм стратегії 2.");
}
}
/// <summary>
/// Контекст, використовує стратегію для вирішення свого завдання.
/// </summary>
public class Context
{
/// <summary>
/// Посилання на інтерфейс <see cref="IStrategy">IStrategy</see>
/// дозволяє автоматично перемикатися між конкретними реалізаціями
/// (іншими словами, це вибір конкретної стратегії).
/// </summary>
private IStrategy _strategy;
/// <summary>
/// Конструктор контексту.
/// Ініціалізує об'єкт стратегією.
/// </summary>
/// <param name="strategy">
/// Стратегія.
/// </param>
public Context(IStrategy strategy)
{
_strategy = strategy;
}
/// <summary>
/// Метод для установки стратегії.
/// Служить для зміни стратегії під час виконання.
/// В C# може бути реалізований так само як властивість запису.
/// </summary>
/// <param name="strategy">
/// Нова стратегія.
/// </param>
public void SetStrategy(IStrategy strategy)
{
_strategy = strategy;
}
/// <summary>
/// Деяка функціональність контексту, яка вибирає
/// стратегію і використовує її для вирішення свого завдання.
/// </summary>
public void ExecuteOperation()
{
_strategy.Algorithm();
}
}
/// <summary>
/// Клас додатка.
/// У даному прикладі виступає як клієнт контексту.
/// </summary>
public static class Program
{
/// <summary>
/// Точка входу в програму.
/// </summary>
public static void Main()
{
// Створюємо контекст і ініціалізували його першої стратегією.
Context context = new Context(new ConcreteStrategy1());
// Виконуємо операцію контексту, яка використовує першу стратегію.
context.ExecuteOperation();
// Замінюємо в контексті першу стратегію другою.
context.SetStrategy(new ConcreteStrategy2());
// Виконуємо операцію контексту, яка тепер використовує другу стратегію.
context.ExecuteOperation();
}
}
}
Приклади на D
import std.stdio;
interface IStrategy
{
int Action(int a, int b);
}
class TAddition: IStrategy
{
public int Action(int a, int b)
{
return a+b;
}
}
class TSubtraction: IStrategy
{
public int Action(int a, int b)
{
return a-b;
}
}
class TContexet
{
private:
int a, b;
IStrategy strategy;
public:
void SetAB(int a, int b)
{
TContexet.a = a;
TContexet.b = b;
};
void SetStrategy(IStrategy strategy)
{
TContexet.strategy = strategy;
}
int Action()
{
return strategy.Action(a, b);
}
}
void main()
{
TContexet context = new TContexet;
context.SetAB(10, 5);
context.SetStrategy(new TAddition);
writeln(context.Action()); // 15
context.SetStrategy(new TSubtraction);
writeln(context.Action()); // 5
}
Приклад на Delphi
program Strategy_pattern;
{$APPTYPE CONSOLE}
type
IStrategy = interface
['{6105F24C-E5B2-47E5-BE03-835A894DEB42}']
procedure Algorithm;
end;
TConcreteStrategy1 = class(TInterfacedObject, IStrategy)
public
procedure Algorithm;
end;
procedure TConcreteStrategy1.Algorithm;
begin
Writeln('TConcreteStrategy1.Algorithm');
end;
type
TConcreteStrategy2 = class(TInterfacedObject, IStrategy)
public
procedure Algorithm;
end;
procedure TConcreteStrategy2.Algorithm;
begin
Writeln('TConcreteStrategy2.Algorithm');
end;
type
TContext = class
private
FStrategy: IStrategy;
public
procedure ContextMethod;
property Strategy: IStrategy read FStrategy write FStrategy;
end;
procedure TContext.ContextMethod;
begin
FStrategy.Algorithm;
end;
var
Context: TContext;
begin
Context := TContext.Create;
try
Context.Strategy := TConcreteStrategy1.Create;
Context.ContextMethod;
Context.Strategy := TConcreteStrategy2.Create;
Context.ContextMethod;
finally
Context.Free;
end;
end.
Приклади на Javascript
// "інтерфейс" Strategy
function Strategy() {
this.exec = function() {};
};
// реалізації Strategy
// показ повідомлення в статусному рядку веб-оглядача
// (підтримується не всіма веб-оглядачами)
function StrategyWindowStatus() {
this.exec = function(message) {
window.status = message;
};
};
StrategyWindowStatus.prototype = new Strategy();
StrategyWindowStatus.prototype.constructor = StrategyWindowStatus;
function StrategyNewWindow() {
this.exec = function(message) {
var win = window.open("", "_blank");
win.document.write("<html>"+ message +"</html>");
};
};
StrategyNewWindow.prototype = new Strategy();
StrategyNewWindow.prototype.constructor = StrategyNewWindow;
// показ повідомлення за допомогою модального вікна
function StrategyAlert() {
this.exec = function(message) {
alert(message);
};
};
StrategyAlert.prototype = new Strategy();
StrategyAlert.prototype.constructor = StrategyAlert;
// Context
function Context(strategy) {
this.exec = function(message) {
strategy.exec(message);
};
}
// Використання
var showInWindowStatus = new Context( new StrategyWindowStatus() );
var showInNewWindow = new Context( new StrategyNewWindow() );
var showInAlert = new Context( new StrategyAlert() );
showInWindowStatus.exec("повідомлення");
showInNewWindow.exec("повідомлення");
showInAlert.exec("повідомлення");
Приклад з використанням динамічних (first-class) функцій
function Context(fn) {
this.exec = function() {
fn.apply(this, arguments || []);
};
};
var showInWindowStatus = new Context( function(message) {
window.status = message;
} );
var showInNewWindow = new Context( function(message) {
var win = window.open("", "_blank");
win.document.write("<html>"+ message +"</html>");
} );
var showInAlert = new Context( function(message) {
alert(message);
} );
showInWindowStatus.exec("повідомлення");
showInNewWindow.exec("повідомлення");
showInAlert.exec("повідомлення");
Приклади на PHP5
<?php
interface NamingStrategy
{
function createName($filename);
}
class ZipFileNamingStrategy implements NamingStrategy
{
function createName($filename)
{
return "http://downloads.foo.bar/{$filename}.zip";
}
}
class TarGzFileNamingStrategy implements NamingStrategy
{
function createName($filename)
{
return "http://downloads.foo.bar/{$filename}.tar.gz";
}
}
class Context
{
private $namingStrategy;
function __construct(NamingStrategy $strategy)
{
$this->namingStrategy = $strategy;
}
function execute()
{
$url[] = $this->namingStrategy->createName("Calc101");
$url[] = $this->namingStrategy->createName("Stat2000");
return $url;
}
}
if (strstr($_SERVER["HTTP_USER_AGENT"], "Win"))
$context = new Context(new ZipFileNamingStrategy());
else
$context = new Context(new TarGzFileNamingStrategy());
$context->execute();
?>
Приклад на Python
class People(object):
tool = None
def __init__(self, name):
self.name = name
def setTool(self, tool):
self.tool = tool
def write(self, text):
self.tool.write(self.name, text)
class ToolBase:
"""
Сімейство алгоритмів `Інструмент написання`
"""
def write(self, name, text):
raise NotImplementedError
class PenTool(ToolBase):
"""Ручка"""
def write(self, name, text):
print u'%s (ручкою) %s' % (name, text)
class BrushTool(ToolBase):
"""Пензель"""
def write(self, name, text):
print u'%s (пензлем) %s' % (name, text)
class Student(People):
"""Студент"""
tool = PenTool()
class Painter(People):
"""Художник"""
tool = BrushTool()
alexandr = Student(u'Олександр')
alexandr.write(u'Пишу лекцію про шаблон Стратегія')
# Олександр (ручкою) Пишу лекцію про шаблон Стратегія
solomia = Painter(u'Соломія')
solomia.write(u'Малюю ілюстрацію до шаблону Стратегія')
# Соломія (пензлем) Малюю ілюстрацію до шаблону Стратегія
# Соломія вирішила стати студентом
solomia.setTool(PenTool())
solomia.write(u'Ні, вже краще я напишу конспект')
# Соломія (ручкою) Ні, вже краще я напишу конспект
Висновки
Останнім часом розроблено багато мов програмування, але в кожній з них для досягнення найкращого результату роботи необхідно використовувати шаблони програмування, одним з яких є Стратегія (Strategy).
Література
- Bishop, Judith. C# 3.0 Design Patterns. Sebastopol, California: O'Reilly, 2008.
- Tomas Petricek, Jon Skeet. Functional Programming for the Real World. б.м.: Manning Publications, 2010.