Паттерн “Стратегия” (Strategy pattern, Policy pattern1 )
Ссылки на описания остальных паттернов расположены здесь .
Содержание
Введение Необходимость паттерна “Стратегия” возникает, довольно часто, например:
- Когда вызывающий код не должен знать о деталях реализации используемого алгоритма.
- Когда во время работы программы необходимо подменять используемый алгоритм.
Данный паттерн является фундаметальным и многие из паттернов, которые будут рассмотрены в дальнейшем являются его частными случаями. Кроме того, любой ООП-программист применяет его каждый раз, когда использует наследование.
Ниже представлена UML-диаграмма данного паттерна.
Рассмотрим несколько задач, иллюстрирующих его применение (пока только одну ^_^').
Выбор элементов из последовательности целых чисел.
В данном примере воспользуемся паттерном “Стратегия” для реализация выборки элементов из последовательности целых чисел по следующим критериям:
- Выборка четных элементов
- Выборка нечетных элементов
- Выборка элементов из отрезка
**C#**
Определим интерфейс для стратегии выбора элемента.
public interface IIntegerSelector
{
IEnumerable<int> Select(IEnumerable<int> source);
}
Стратегия выбора четных чисел.
public class EvenNumbersSelector : IIntegerSelector
{
public IEnumerable<int> Select(IEnumerable<int> source)
{
var sourceEnumerator = source.GetEnumerator();
while (sourceEnumerator.MoveNext())
{
if (sourceEnumerator.Current % 2 == 0)
yield return sourceEnumerator.Current;
}
// Or using Linq
//return source.Where(el => el % 2 == 0);
}
}
Стратегия выбора нечетных чисел.
public class OddNumbersSelector : IIntegerSelector
{
public IEnumerable<int> Select(IEnumerable<int> source)
{
var sourceEnumerator = source.GetEnumerator();
while (sourceEnumerator.MoveNext())
{
if (sourceEnumerator.Current % 2 == 0)
continue;
yield return sourceEnumerator.Current;
}
// Or using Linq
//return source.Where(el => el % 2 != 0);
}
}
Стратегия выбора чисел из отрезка.
public class NumbersFormRangeSelector : IIntegerSelector
{
public int RangeStart { get; }
public int RangeEnd { get; }
public NumbersFormRangeSelector(int rangeStart, int rangeEnd)
{
if (rangeStart >= rangeEnd)
throw new ArgumentOutOfRangeException();
RangeStart = rangeStart;
RangeEnd = rangeEnd;
}
public IEnumerable<int> Select(IEnumerable<int> source)
{
var sourceEnumerator = source.GetEnumerator();
while (sourceEnumerator.MoveNext())
{
if (sourceEnumerator.Current >= RangeStart && sourceEnumerator.Current <= RangeEnd)
yield return sourceEnumerator.Current;
}
// Or using Linq
//return source.Where(el => el >= RangeStart && el <= RangeEnd);
}
}
Используем реализованные стратегии.
class Program
{
public static void PrintSelectedElements(List<int> source, IIntegerSelector selector)
{
Console.WriteLine($"[{source.Select(el => el.ToString()).Aggregate((one, two) => one + ", " + two)}]");
foreach (var element in selector.Select(source))
{
Console.WriteLine($"Selected element: {element}");
}
}
static void Main(string[] args)
{
var sourceList = new List<int>() { 1, 2, 3, 4, 5, 6, -4, -1, -455, 2, 1, 456, 783, 12, 45, 90, 24 };
Console.WriteLine("Selecting even numbers");
PrintSelectedElements(sourceList, new EvenNumbersSelector());
Console.WriteLine("Selecting odd numbers");
PrintSelectedElements(sourceList, new OddNumbersSelector());
Console.WriteLine("Selecting numbers in range [1, 20]");
PrintSelectedElements(sourceList, new NumbersFormRangeSelector(1, 20));
}
}
**Python**
Определим базовый класс для стратегии выбора элемента.
class BaseSelector:
def select(self, source: list) -> list:
pass
Стратегия выбора четных чисел.
class EvenNumbersSelector(BaseSelector):
def select(self, source: list) -> list:
result = []
for val in source:
if val % 2 == 0:
result.append(val)
return result
Стратегия выбора нечетных чисел.
from selectors.BaseSeletor import BaseSelector
class OddNumbersSelector(BaseSelector):
def select(self, source: list) -> list:
result = []
for val in source:
if val % 2 != 0:
result.append(val)
return result
Стратегия выбора чисел из отрезка.
class NumbersFormRangeSelector(BaseSelector):
segmentStart = 0
segmentEnd = 0
def __init__(self, segmentstart:int, segmentend:int):
self.segmentStart = segmentstart
self.segmentEnd = segmentend
if self.segmentStart >= self.segmentEnd:
raise ValueError()
def select(self, source: list) -> list:
result = []
for val in source:
if self.segmentStart <= val <= self.segmentEnd:
result.append(val)
return result
Используем реализованные стратегии.
def print_selected_elements(source: list, strategy: BaseSelector):
print(source)
for val in strategy.select(source):
print("Selected element: " + str(val))
sourceList = [1, 2, 3, 4, 5, 6, -4, -1, -455, 2, 1, 456, 783, 12, 45, 90, 24]
print("Selecting even numbers")
print_selected_elements(sourceList, EvenNumbersSelector())
print("Selecting odd numbers")
print_selected_elements(sourceList, OddNumbersSelector())
print("Selecting numbers in range [1, 20]")
print_selected_elements(sourceList, NumbersFormRangeSelector(1, 20))
**C**
Определим тип для стратегии выбора элемента. Получается, конечно, не так изящно - приходится задавать самый общий случай и для выбора четных и нечетных чисел будет передаваться две неиспользуемые переменные. Есть ещё второй способ реализация паттерна стратегия с помощью изменения состояния3 , но, на мой взгляд, текущий вариант всё же красивее.
typedef int (*IntegerSelectorStrategy) (int*,int,int,int, int**);
Стратегия выбора четных чисел.
int EvenNumbersSelector(int* source, int len, int unused1, int unused2, int** resultArray)
{
int numberOfPassed = 0;
for(int i=0; i<len; ++i)
{
if(source[i] % 2 == 0)
numberOfPassed++;
}
(*resultArray) = (int*)malloc(numberOfPassed * sizeof(int));
int* resultArrayPointerCopy = (*resultArray);
for(int i=0; i<numberOfPassed; ++source)
{
if((*source) % 2 == 0)
{
(*resultArrayPointerCopy) = (*source);
++resultArrayPointerCopy;
++i;
}
}
return numberOfPassed;
}
Стратегия выбора нечетных чисел.
int OddNumbersSelector(int* source, int len, int unused1, int unused2, int** resultArray)
{
int numberOfPassed = 0;
for(int i=0; i<len; ++i)
{
if(source[i] % 2 != 0)
numberOfPassed++;
}
(*resultArray) = (int*)malloc(numberOfPassed * sizeof(int));
int* resultArrayPointerCopy = (*resultArray);
for(int i=0; i<numberOfPassed; ++source)
{
if((*source) % 2 != 0)
{
(*resultArrayPointerCopy) = (*source);
++resultArrayPointerCopy;
++i;
}
}
return numberOfPassed;
}
Стратегия выбора чисел из отрезка.
int NumbersFormRangeSelector(int* source, int len, int segmentStart, int segmentEnd, int** resultArray)
{
int numberOfPassed = 0;
for(int i=0; i<len; ++i)
{
if(source[i] >= segmentStart && source[i] <= segmentEnd)
numberOfPassed++;
}
(*resultArray) = (int*)malloc(numberOfPassed * sizeof(int));
int* resultArrayPointerCopy = (*resultArray);
for(int i=0; i<numberOfPassed; ++source)
{
if((*source) >= segmentStart && (*source) <= segmentEnd)
{
(*resultArrayPointerCopy) = (*source);
++resultArrayPointerCopy;
++i;
}
}
return numberOfPassed;
}
Используем реализованные стратегии.
void PrintSelectedElements(int* source, int len, int segmentStart, int segmentEnd, IntegerSelectorStrategy strategy)
{
printf("[");
for(int i=0;i<len-1;i++)
{
printf("%d, ", source[i]);
}
printf("%i]\n", source[len-1]);
int* resultArray;
int lenResultArray = (*strategy)(source, len, segmentStart, segmentEnd, &resultArray);
for(int i=0;i<lenResultArray;i++)
{
printf("Selected element: %d \n", resultArray[i]);
}
}
int main()
{
int sourceArray[17] = { 1, 2, 3, 4, 5, 6, -4, -1, -455, 2, 1, 456, 783, 12, 45, 90, 24};
printf("Selecting even numbers");
PrintSelectedElements(sourceArray, 17, 0, 0, EvenNumbersSelector);
printf("Selecting odd numbers");
PrintSelectedElements(sourceArray, 17, 0, 0, OddNumbersSelector);
printf("Selecting numbers in range [1, 20]");
PrintSelectedElements(sourceArray, 17, 1, 20, NumbersFormRangeSelector);
return 0;
}
**JS [by xaota](https://github.com/xaota)**
class BaseSelector {
select(list) {
return list;
}
}
class EvenNumbersSelector {
select(list) {
return list.filter(n => n % 2 === 0);
}
}
class OddNumbersSelector {
select(list) {
return list.filter(n => n % 2 === 1);
}
}
class NumbersFromRangeSelector {
constructor(begin, end) {
this.begin = begin;
this.end = end;
}
select(list) {
let min = this.begin;
let max = this.end;
return list.filter(n => min <= n && n <= max);
}
}
let sourceList = [1, 2, 3, 4, 5, 6, -4, -1, -455, 2, 1, 456, 783, 12, 45, 90, 24] ;
let even = new EvenNumbersSelector;
let odd = new OddNumbersSelector;
let ranged = new NumbersFromRangeSelector(1, 20);
even.select(sourceList);
odd.select(sourceList);
ranged.select(sourceList);
Весь код использованный в проекте доступен на GitHub .
Литература
[1]: Strategy pattern. From Wikipedia [2]: Тепляков С. Паттерны проектирования на платформе .NET – СПб.: Питер, 2015. — 28-37 c. [3]: Patterns in C - Part 3: STRATEGY [4]: Design Patterns: Elements of Reusable Object-Oriented Software