Где выгода в использовании шаблона стратегии? - PullRequest
12 голосов
/ 05 октября 2008

Я посмотрел на это объяснение в Википедии , в частности, на примере C ++, и не смог распознать разницу между определением трех классов, созданием экземпляров и их вызовом, и этим примером. То, что я увидел, просто включило в процесс два других класса и не смог понять, в чем будет польза. Теперь я уверен, что упускаю что-то очевидное (дрова для деревьев) - может, кто-нибудь объяснит это на конкретном реальном примере?


Что я могу сделать из ответов до сих пор, мне кажется, это просто более сложный способ сделать это:

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[Edit-обновление] Функция, на которую я ссылаюсь выше, заменяется другим классом, в котором MoveAlong будет атрибутом, который устанавливается в соответствии с потребностями на основе алгоритма, реализованного в этом новом классе. (Подобно тому, что продемонстрировано в принятом ответе.)


[Редактировать-обновить] Заключение

У Паттерна Стратегии есть свои применения, но я твердо верю в KISS и склоняюсь к более простым и менее запутанным методам. Главным образом, поскольку я хочу передать легко поддерживаемый код (и, скорее всего, я буду тем, кто должен будет внести изменения!).

Ответы [ 8 ]

21 голосов
/ 05 октября 2008

Смысл в том, чтобы разделить алгоритмы на классы, которые можно подключить во время выполнения. Например, допустим, у вас есть приложение с часами. Существует много разных способов рисования часов, но по большей части базовая функциональность одинакова. Таким образом, вы можете создать интерфейс отображения часов:

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

Тогда у вас есть класс часов, который подключен к таймеру и обновляет отображение часов раз в секунду. Так что у вас будет что-то вроде:

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

Затем во время выполнения вы создаете свои часы с соответствующим классом отображения. то есть у вас могут быть ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian, все реализующие интерфейс IClockDisplay.

Таким образом, вы можете позже добавить любой тип нового отображения часов, создав новый класс без необходимости связываться с вашим классом Clock и без необходимости переопределять методы, которые могут быть беспорядочными для обслуживания и отладки.

10 голосов
/ 05 октября 2008

В Java для расшифровки используется входной поток шифра:

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

Но поток шифрования не знает, какой алгоритм шифрования вы намереваетесь использовать, размер блока, стратегию заполнения и т. Д. ... Новые алгоритмы будут добавляться постоянно, поэтому их жесткое кодирование нецелесообразно. Вместо этого мы передаем объект стратегии Cipher , чтобы сообщить ему, как выполнить расшифровку ...

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

Обычно вы используете шаблон стратегии каждый раз, когда у вас есть объект, который знает , что нужно сделать, но не , как , чтобы сделать это. Другим хорошим примером являются менеджеры компоновки в Swing, хотя в этом случае это не сработало, см. Totally GridBag для забавной иллюстрации.

Примечание: здесь работают два шаблона, так как обтекание потоков в потоках является примером Decorator .

5 голосов
/ 05 октября 2008

Существует разница между стратегией и решением / выбором. Большую часть времени мы будем обрабатывать решения / выборы в нашем коде и реализовывать их, используя конструкции if () / switch (). Шаблон стратегии полезен, когда необходимо отделить логику / алгоритм от использования.

В качестве примера рассмотрим механизм опроса, в котором разные пользователи будут проверять наличие ресурсов / обновлений. Теперь мы можем захотеть, чтобы некоторые привилегированные пользователи были уведомлены в более короткие сроки или с более подробной информацией. По сути, используемая логика меняется в зависимости от ролей пользователя. Стратегия имеет смысл с точки зрения дизайна / архитектуры, на более низких уровнях детализации она всегда должна подвергаться сомнению.

4 голосов
/ 05 октября 2008

Шаблон стратегии позволяет вам использовать полиморфизм, не расширяя ваш основной класс. По сути, вы помещаете все переменные части в интерфейс и реализации стратегии, а также делегаты основного класса. Если ваш основной объект использует только одну стратегию, это почти то же самое, что иметь абстрактный (чисто виртуальный) метод и разные реализации в каждом подклассе.

Стратегический подход предлагает некоторые преимущества:

  • вы можете изменить стратегию во время выполнения - сравните это с изменением типа класса во время выполнения, что намного сложнее, специфично для компилятора и невозможно для не виртуальных методов
  • один основной класс может использовать более одной стратегии, что позволяет комбинировать их несколькими способами. Рассмотрим класс, который обходит дерево и оценивает функцию на основе каждого узла и текущего результата. У вас может быть стратегия ходьбы (первая по глубине или первая по ширине) и стратегия вычисления (некоторый функтор - то есть «считать положительные числа» или «сумма»). Если вы не используете стратегии, вам нужно будет реализовать подкласс для каждой комбинации ходьбы / расчета.
  • код легче поддерживать, так как изменение или понимание стратегии не требует понимания всего основного объекта

Недостатком является то, что во многих случаях шаблон стратегии является излишним - у оператора switch / case есть причина. Попробуйте начать с простых операторов потока управления (switch / case или if), тогда только при необходимости переходите к иерархии классов и, если у вас более одного измерения изменчивости, извлекайте стратегии из нее. Указатели функций находятся где-то посередине этого континуума.

Рекомендуемое значение:

2 голосов
/ 08 октября 2008

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

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

2 голосов
/ 05 октября 2008

Этот шаблон проектирования позволяет инкапсулировать алгоритмы в классах.

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

Например, представьте приложение, которое должно сохранить изображение в файл; изображение может быть сохранено в разных форматах (PNG, JPG ...). Все алгоритмы кодирования будут реализованы в разных классах, использующих один и тот же интерфейс. Клиентский класс выберет один в зависимости от предпочтений пользователя.

1 голос
/ 05 октября 2008

В примере из Википедии эти экземпляры могут быть переданы в функцию, которой не нужно заботиться о том, к какому классу принадлежат эти экземпляры. Функция просто вызывает execute переданного объекта и знает, что произойдет правильное.

Типичным примером шаблона стратегии является работа файлов в Unix. Имея файловый дескриптор, вы можете читать из него, писать в него, опрашивать его, искать по нему, отправлять ioctl s и т. Д., Без необходимости знать, имеете ли вы дело с файлом, каталогом, каналом, сокет, устройство и т. д. (Конечно, некоторые операции, такие как поиск, не работают с каналами и сокетами. Но чтение и запись в этих случаях будут работать нормально.)

Это означает, что вы можете написать универсальный код для обработки всех этих различных типов «файлов» без необходимости писать отдельный код для работы с файлами по сравнению с каталогами и т. Д. Ядро Unix заботится о делегировании вызовов нужному коду.

Теперь это шаблон стратегии, используемый в коде ядра, но вы не указали, что это должен быть пользовательский код, просто пример из реальной жизни. : -)

0 голосов
/ 21 февраля 2017

Шаблон стратегии работает на простой идее, т. Е. «Состав предпочтения по наследованию», так что стратегию / алгоритм можно изменить во время выполнения. Чтобы проиллюстрировать это, давайте возьмем пример, где нам нужно зашифровать различные сообщения в зависимости от их типа, например, MailMessage, ChatMessage и т. Д.

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

Теперь во время выполнения вы можете создавать экземпляры различных сообщений, унаследованных от CMessage (например, CMailMessage: public CMessage) с различными шифровалами (например, CDESEncryptor: public CEncryptor)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
...