Стратегія (шаблон проєктування)
Стратегія (англ. 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).
Джерела[ред. | ред. код]
Design Patterns: Elements of Reusable Object-Oriented Software [Архівовано 9 листопада 2012 у Wayback Machine.]
Література[ред. | ред. код]
- 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.
Посилання[ред. | ред. код]
- modis.ispras.ru/Lizorkin/private/patterns.pdf
- http://code.tutsplus.com/uk/tutorials/design-patterns-the-strategy-pattern--cms-22796 [Архівовано 5 березня 2016 у Wayback Machine.]