В чем выгода от использования Шаблона Стратегии?

StackOverflow https://stackoverflow.com/questions/171776

  •  05-07-2019
  •  | 
  •  

Вопрос

Я просмотрел на это объяснение в Википедии, в частности, образец C ++, и не в состоянии распознать разницу между простым определением 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?)

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


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

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

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

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

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

ПРИМЕЧАНИЕ:Здесь работают два шаблона, поскольку обертывание потоков в streams является примером Декоратор.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top