Стратегия

Паттерны проектирования / Поведенческие паттерны
image

type
  //the algorithm is swappable
  IStrategy = interface
    function DoOperation(
      A, B: Integer): Integer;
  end;

  TAddStrategy = class(TInterfacedObject,
    IStrategy)
    function DoOperation(
      A, B: Integer): Integer;
  end;

  TSubtractStrategy = class(
    TInterfacedObject, IStrategy)
    function DoOperation(
      A, B: Integer): Integer;
  end;

  //context holds a strategy and
  //only calls it
  TCalc = class
  private
    FStrategy: IStrategy;
  public
    function Execute(
      A, B: Integer): Integer;
    procedure SetStrategy(
      const Strategy: IStrategy);
  end;

function TAddStrategy.DoOperation(
  A, B: Integer): Integer;
begin
  Result := A + B;
end;

function TSubtractStrategy.DoOperation(
  A, B: Integer): Integer;
begin
  Result := A - B;
end;

function TCalc.Execute(
  A, B: Integer): Integer;
begin
  if FStrategy = nil then
    Result := 0
  else
    Result := FStrategy.DoOperation(A, B);
end;

procedure TCalc.SetStrategy(
  const Strategy: IStrategy);
begin
  FStrategy := Strategy;
end;

var
  Calc: TCalc;
begin
  Calc := TCalc.Create;
  WriteLn('result1 is ', Calc.Execute(5, 3));
  //result1 is 0

  Calc.SetStrategy(TAddStrategy.Create);
  WriteLn('result2 is ', Calc.Execute(5, 3));
  //result2 is 8

  Calc.SetStrategy(TSubtractStrategy.Create);
  WriteLn('result3 is ', Calc.Execute(5, 3));
  //result3 is 2
end.


Определяет семейство алгоритмов, инкапсулируя их и позволяя подставлять один вместо другого. Можно менять алгоритм независимо от клиента, который им пользуется.
Read more

Задача. Магазин считает стоимость доставки: по весу, по расстоянию или по фиксированной цене. Если один метод держит все способы в цепочке условий, он растёт с каждым новым способом. Правка одного способа может сломать другие, и ни один способ нельзя проверить отдельно.

Идея. Каждый способ сделать работу становится отдельным объектом - стратегией. У всех стратегий одинаковый вид: те же входные данные и тот же тип результата. Объект, которому нужна эта работа, называют контекстом. Он хранит одну стратегию и вызывает её, не зная, какая именно ему досталась. Стратегию выбирает клиентский код и может заменить её в любой момент.

Как читать пример. Каждая конкретная стратегия - это один алгоритм, и все они принимают одинаковые входные данные. Клиентский код выбирает стратегию. Код, который пользуется стратегией, только вызывает её и не знает, какую получил. В большинстве примеров одни и те же два числа дают другой результат после каждой смены стратегии. Большинство из них отвечают и на случай, когда стратегии нет. В некоторых языках стратегия - просто функция.

Когда применять. Стратегия нужна, когда одну работу можно сделать несколькими способами, а выбор зависит от пользователя, данных или настроек. Для одного алгоритма, который не будет меняться, она ни к чему. Стратегия похожа на Состояние, но разница в том, кто делает выбор. Здесь алгоритм выбирает снаружи клиентский код, и стратегии ничего не знают друг о друге. В Состоянии объект сам переходит из состояния в состояние.

Частая ошибка. Заставить контекст проверять, какая стратегия у него в руках. Условия возвращаются, и паттерн теряет смысл. Контекст должен только вызывать стратегию.