Подтвердить что ты не робот

Где преимущество в использовании шаблона стратегии?

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


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

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 и буду стремиться к более простым и менее обфускаторным методам. В основном, поскольку я хочу передать легко поддерживаемый код (и, поскольку я, скорее всего, буду тем, кто должен внести изменения!).

4b9b3361

Ответ 1

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

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.

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Рекомендуемое чтение:

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

Шаблон стратегии работает с простой идеей, то есть "Favor Composition over Inheritance", чтобы стратегия/алгоритм можно было изменить во время выполнения. Чтобы проиллюстрировать, давайте возьмем пример, где нам нужно зашифровать разные сообщения на основе его типа, например. 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();