Стратегія (шаблон проєктування) — Вікіпедія
Стратегія (англ. 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.]