- Стратегия (шаблон проектирования)
-
Шаблон проектирования Стратегия Strategy Тип: поведенческий
Назначение: позволяет использовать различные бизнес-правила или алгоритмы в зависимости от контекста.
Применяется в случаях: в одном и том же случае, в зависимости от текущего состояния системы или её окружения, используются различные алгоритмы.
Плюсы: - инкапсуляция реализации различных алгоритмов, система становится независимой от возможных изменений бизнес-правил;
- вызов всех алгоритмов одним стандартным образом;
- отказ от использования переключателей и/или условных операторов.
Минусы: создание дополнительных классов
Родственные шаблоны: Описан в Design Patterns Да
Стратегия, Strategy — поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путем определения соответствующего класса. Шаблон Strategy позволяет менять выбранный алгоритм независимо от объектов-клиентов, которые его используют.
Содержание
Основные характеристики
Задача
По типу клиента (или по типу обрабатываемых данных) выбрать подходящий алгоритм, который следует применить. Если используется правило, которое не подвержено изменениям, нет необходимости обращаться к шаблону «стратегия».
Мотивы
- Программа должна обеспечивать различные варианты алгоритма или поведения
- Нужно изменять поведение каждого экземпляра класса
- Необходимо изменять поведение объектов на стадии выполнения
- Введение интерфейса позволяет классам-клиентам ничего не знать о классах, реализующих этот интерфейс и инкапсулирующих в себе конкретные алгоритмы
Способ решения
Отделение процедуры выбора алгоритма от его реализации. Это позволяет сделать выбор на основании контекста.
Участники
- Класс
Strategy
определяет, как будут использоваться различные алгоритмы. - Конкретные классы
ConcreteStrategy
реализуют эти различные алгоритмы. - Класс
Context
использует конкретные классыConcreteStrategy
посредством ссылки на конкретный тип абстрактного классаStrategy
. КлассыStrategy
иContext
взаимодействуют с целью реализации выбранного алгоритма (в некоторых случаях классуStrategy
требуется посылать запросы классуContext
). КлассContext
пересылает классуStrategy
запрос, поступивший от его класса-клиента.
Следствия
- Шаблон Strategy определяет семейство алгоритмов.
- Это позволяет отказаться от использования переключателей и/или условных операторов.
- Вызов всех алгоритмов должен осуществляться стандартным образом (все они должны иметь одинаковый интерфейс).
Реализация
Класс, который использует алгоритм (
Context
), включает абстрактный класс (Strategy
), обладающий абстрактным методом, определяющим способ вызова алгоритма. Каждый производный класс реализует один требуемый вариант алгоритма.Замечание: метод вызова алгоритма не должен быть абстрактным, если требуется реализовать некоторое поведение, принимаемое по умолчанию.
Полезные сведения
- и стратегия, и декоратор может применяться для изменения поведения конкретных классов. Достоинство стратегии в том, что интерфейс кастомизации не совпадает с публичным интерфейсом и может быть куда более удобным, а недостаток в том, что для использования стратегии необходимо изначально проектировать класс с возможностью регистрации стратегий.
Использование
Архитектура Microsoft WDF основана на этом паттерне. У каждого объекта "драйвер" и "устройство" есть неизменяемая часть, вшитая в систему, в которой регистрируется изменяемая часть (стратегия), написанная в конкретной реализации. Изменяемая часть может быть и вовсе пустой, что даст ничего не делающий драйвер, но при этом способный участвовать в PnP и управлении питанием.
Библиотека ATL содержит в себе набор классов threading model, которые являются стратегиями (различными реализациями Lock/Unlock, которые потом используются основными классами системы). При этом в этих стратегиях используется статический полиморфизм через параметр шаблона, а не динамический полиморфизм через виртуальные методы.
Примеры
Пример на 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++
Пример реализацииclass Strategy { public: Strategy(void){} ~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() maxim = Student(u'Максим') maxim.write(u'Пишу лекцию о паттерне Стратегия') # Максим (ручкой) Пишу лекцию о паттерне Стратегия sasha = Painter(u'Саша') sasha.write(u'Рисую иллюстрацию к паттерну Стратегия') # Саша (кистью) Рисую иллюстрацию к паттерну Стратегия # Саша решил стать студентом sasha.setTool(PenTool()) sasha.write(u'Нет, уж лучше я напишу конспект') # Саша (ручкой) Нет, уж лучше я напишу конспект
Пример на VB.NET
Пример реализацииNamespace DesignPatterns.Behavioral.Strategy ''' <summary> ''' Интерфейс «Стратегия» определяет функциональность (в данном примере это метод ''' <see cref="Algorithm">Algorithm</see>), которая должна быть реализована ''' конкретными классами стратегий. Другими словами, метод интерфейса определяет ''' решение некой задачи, а его реализации в конкретных классах стратегий определяют, ''' КАК, КАКИМ ПУТЁМ эта задача будет решена. ''' </summary> Public Interface IStrategy Sub Algorithm() End Interface ''' <summary> ''' Первая конкретная реализация-стратегия. ''' </summary> Public Class ConcreteStrategy1 Implements IStrategy Public Sub Algorithm() Implements IStrategy.Algorithm Console.WriteLine("Выполняется алгоритм стратегии 1.") End Sub End Class ''' <summary> ''' Вторая конкретная реализация-стратегия. ''' Реализаций может быть сколько угодно много. ''' </summary> Public Class ConcreteStrategy2 Implements IStrategy Public Sub Algorithm() Implements IStrategy.Algorithm Console.WriteLine("Выполняется алгоритм стратегии 2.") End Sub End Class ''' <summary> ''' Контекст, использующий стратегию для решения своей задачи. ''' </summary> Public Class Context ''' <summary> ''' Ссылка на интерфейс <see cref="IStrategy">IStrategy</see> ''' позволяет автоматически переключаться между конкретными реализациями ''' (другими словами, это выбор конкретной стратегии). ''' </summary> Private _strategy As IStrategy ''' <summary> ''' Конструктор контекста. ''' Инициализирует объект стратегией. ''' </summary> ''' <param name="strategy"> ''' Стратегия. ''' </param> Public Sub New(ByVal strategy As IStrategy) _strategy = strategy End Sub ''' <summary> ''' Метод для установки стратегии. ''' Служит для смены стратегии во время выполнения. ''' В C# может быть реализован также как свойство записи. ''' </summary> ''' <param name="strategy"> ''' Новая стратегия. ''' </param> Public Sub SetStrategy(ByVal strategy As IStrategy) _strategy = strategy End Sub ''' <summary> ''' Некоторая функциональность контекста, которая выбирает ''' стратегию и использует её для решения своей задачи. ''' </summary> Public Sub ExecuteOperation() _strategy.Algorithm() End Sub End Class ''' <summary> ''' Класс приложения. ''' В данном примере выступает как клиент контекста. ''' </summary> Public NotInheritable Class Program Private Sub New() End Sub ''' <summary> ''' Точка входа в программу. ''' </summary> Shared Sub Main() ' Создём контекст и инициализируем его первой стратегией. Dim context As New Context(New ConcreteStrategy1()) ' Выполняем операцию контекста, которая использует первую стратегию. context.ExecuteOperation() ' Заменяем в контексте первую стратегию второй. context.SetStrategy(New ConcreteStrategy2()) ' Выполняем операцию контекста, которая теперь использует вторую стратегию. context.ExecuteOperation() Console.Read() End Sub End Class End Namespace
Пример на ActionScript
Пример реализацииpackage patterns.strategy { public interface IAlgorithm { function get length () : int function apply (scope:Object = null, args:Array = null):* } } package patterns.strategy { public class Strategy { protected var _protect:Boolean; protected var _isFunctionStrategy:Boolean; protected var _numberOfArguments:int; protected var _algorithm:Object; protected var _key:String; public function Strategy(key:String, algorithm:Object, protect:Boolean = false) { _protect = protect; _key = key; _algorithm = algorithm; init(); } protected function init():void { _isFunctionStrategy = _algorithm is Function; _numberOfArguments = _algorithm.length } public function execute(args:Array = null):* { return _algorithm.apply(this, args); } public function get isFunctionStrategy():Boolean { return _isFunctionStrategy; } public function get numberOfArguments():int { return _numberOfArguments; } public function get key():String { return _key; } public function get protect():Boolean { return _protect; } } } package patterns.strategy { public class SimpleAlgorithm extends Object implements IAlgorithm { private var _executeMethod:Function; public function SimpleAlgorithm() { super(); if (!this.hasOwnProperty('execute')) throw(new Error('strategy algorithm or child must have public field "execute"')); } public function apply(scope:Object = null, args:Array = null):* { return this['execute'].apply(scope, args); } public function get length():int { return this['execute'].length; } } } package patterns.strategy { public class StrategyController { protected var strategies:/*Strategy*/Object; public function StrategyController() { init(); } private function init():void { strategies = { }; } public function addStrategy(strategy:Strategy):void { var key:String = strategy.key; var isProtected:Boolean = false; if (hasStrategy(key)) isProtected = strategies[key].protect; if (!isProtected) { strategies[key] = strategy; } else { throw(new Error('try to override protected strategy')); } } public function removeStrategy(key:String):void { strategies[key] = null; } public function hasStrategy(key:String):Boolean { return strategies.hasOwnProperty(key); } public function crateNewStrategy(key:String, algorithm:*, protect:Boolean = false):void { addStrategy(new Strategy(key, algorithm, protect)); } public function execute(key:String, ...args:Array):* { if(hasStrategy(key)) return strategies[key].execute.apply(null, args || null); else return null; } } } package patterns.strategy { public class SomeALG extends SimpleAlgorithm { public function SomeALG() { super(); } public function execute(a1:int, a2:int, a3:int):* { return math(a1, a2, a3); } private function math(a1:int, a2:int, a3:int):int { return a1 + a2 + a3; } } }
Источники информации
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1 (также ISBN 5-272-00355-1)
- Шаллоуей, Алан, Тротт, Джейм, Р. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию: Пер. с англ. —М.: Издательский дом «Вильямс», 2002. —288 с. ISBN 5-8459-0301-7
- Grand, Mark. Шаблоны проектирования в Java: Пер. с англ. — М.: Новое знание, 2004. — 559 с. ISBN 5-94735-047-5
Шаблоны проектирования Основные Порождающие Структурные Поведенческие Интерпретатор • Итератор • Команда • Наблюдатель • Посетитель • Посредник • Состояние • Стратегия • Хранитель • Цепочка обязанностей • Шаблонный метод
Блокировка с двойной проверкой • Однопоточное выполнение • Планировщик Категория:- Шаблоны проектирования
Wikimedia Foundation. 2010.