В чем польза от использования стратегии?
Я посмотрел на это объяснение в Википедии, в частности, образец 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?)
[Edit-update] Функция, на которую я ссылаюсь выше, заменяется другим классом, в котором MoveAlong будет атрибутом, который устанавливается в соответствии с потребностями на основе алгоритма, реализованного в этом новом классе. (Аналогично тому, что показано в принятом ответе.)
[Edit-update] вывод
шаблон стратегии имеет его использование, но я твердо верю в KISS и склонен к более простым и менее запутанным методам. В основном потому, что я хочу передать легко поддерживаемый код (и потому, что я, скорее всего, буду тем, кто должен внести изменения!).
8 ответов
дело в том, чтобы отдельные алгоритмы на классы, которые могут быть подключены во время выполнения. Например, скажем, у вас есть приложение, которое включает в себя часы. Есть много разных способов, которыми вы можете нарисовать часы, но по большей части базовая функциональность одинакова. Таким образом, вы можете создать интерфейс отображения часов:
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.
таким образом, вы можете позже добавить любой тип нового дисплея часов, создав новый класс без необходимости возиться с вашим классом часов и без необходимости переопределять методы, которые могут быть грязными для обслуживания и отладки.
в Java вы используете входной поток шифрования для дешифрования так:
String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);
но поток шифрования не знает, какой алгоритм шифрования вы собираетесь использовать или размер блока, стратегию заполнения и т. д... Новые алгоритмы будут добавляться все время, поэтому их жесткое кодирование нецелесообразно. Вместо этого мы переходим в шифр стратегии объекта чтобы рассказать ему, как выполнить расшифровку...
String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);
В общем, вы используете шаблон стратегии в любое время, когда у вас есть объект, который знает что это нужно сделать, но не как сделать это. Другим хорошим примером являются менеджеры макетов в Swing, хотя в этом случае это не сработало так хорошо, см. Полностью GridBag для забавной иллюстрации.
NB: здесь работают два шаблона, так как обертывание потоков в потоках является примером оформителя.
существует разница между стратегией и решением / выбором. Большую часть времени мы будем обрабатывать решения/варианты в нашем коде и реализовывать их, используя конструкции if()/switch (). Шаблон стратегии полезен, когда есть необходимость отделить логику / алгоритм от использования.
в качестве примера подумайте о механизме опроса, где разные пользователи будут проверять наличие ресурсов / обновлений. Теперь мы можем некоторые из priveliged пользователей будет более быструю обработку или с более подробной информацией. Essentailly используемая логика изменяется на основе ролей пользователей. Стратегия имеет смысл с точки зрения дизайна / архитектуры, на более низких уровнях детализации она всегда должна быть поставлена под сомнение.
стратегия шаблон позволяет эксплуатировать polimorphism без продления основного класса. По сути, вы помещаете все переменные части в интерфейс стратегии и реализации и делегаты основного класса к ним. Если ваш основной объект использует только одну стратегию, это почти то же самое, что иметь абстрактный (чистый виртуальный) метод и разные реализации в каждом подклассе.
стратегический подход предлагает некоторые преимущества:
- вы можете изменить стратегия во время выполнения-сравните это с изменением типа класса во время выполнения, что намного сложнее, специфично для компилятора и невозможно для не виртуальных методов
- один основной класс можно использовать более одной стратегии, которая позволяет рекомбинировать их множеством способов. Рассмотрим класс, который ходит по дереву и оценивает функцию на основе каждого узла и текущего результата. У вас может быть стратегия ходьбы (глубина-первая или ширина-первая) и стратегия расчета (некоторый функтор-т. е. " count положительные числа " или "сумма"). Если вы не используете стратегии, вам нужно будет реализовать подкласс для каждой комбинации ходьбы/расчета.
- код легче поддерживать, поскольку изменение или понимание стратегии не требует от вас понимания всего основного объекта
недостатком является то, что во многих случаях шаблон стратегии является излишним - оператор switch/case существует по какой-то причине. Рассмотрите возможность начать с простых операторов потока управления (switch / case или if) тогда только при необходимости переходите к иерархии классов и если у вас есть более одного измерения изменчивости, извлекайте из нее стратегии. Указатели функций находятся где-то посередине этого континуума.
рекомендуемое значение:
один из способов смотреть на это, когда у вас есть различные действия, которые вы хотите выполнить и эти действия определяются во время выполнения. При создании хэш-таблицы или словаря стратегий можно получить стратегии, соответствующие значениям или параметрам команд. Как только ваше подмножество выбрано, вы можете просто повторить список стратегий и выполнить последовательно.
одним из конкретных примеров будет вычисление общей суммы заказа. Ваши параметры или команды базовая цена, местный налог, городской налог, государственный налог, наземная доставка и купонная скидка. Гибкость вступает в игру, когда вы обрабатываете изменение заказов - некоторые государства не будут иметь налог с продаж, в то время как другие заказы должны будут применять купон. Вы можете динамически назначать порядок вычислений. Пока вы учитываете все свои вычисления, вы можете разместить все комбинации без повторной компиляции.
этот шаблон проектирования позволяет инкапсулировать алгоритмы в классах.
класс, который использует стратегию, клиентский класс, отделяется от реализации алгоритма. Можно изменить реализацию алгоритмов или добавить новый алгоритм без необходимости изменения клиента. Это также можно сделать динамически: клиент может выбрать алгоритм, который он будет использовать.
например, представьте себе приложение, которое должно сохранить изображение в файл ; изображение можно сохранить в различных форматах (PNG, JPG ...). Алгоритмы кодирования будут реализованы в разных классах, использующих один и тот же интерфейс. Класс client выберет один в зависимости от предпочтений пользователя.
в Примере Википедии эти экземпляры могут быть переданы в функцию, которой не нужно заботиться о том, к какому классу принадлежат эти экземпляры. Функция просто вызывает execute
на объект прошел, и знаю, что правильная вещь произойдет.
типичным примером шаблона стратегии является то, как файлы работают в Unix. Учитывая дескриптор файла, вы можете читать из него, писать ему, опрашивать его, искать на нем, отправлять ioctl
s к нему и т. д., без необходимости знать, имеете ли вы дело с файлом, каталог, труба, гнездо, прибор, etc. (Конечно, некоторые операции, такие как seek, не работают на трубах и розетках. Но в этих случаях чтение и запись будут работать просто отлично.)
это означает, что вы можете написать общий код для обработки всех этих различных типов "файлов", без необходимости писать отдельный код для работы с файлами и каталогами и т. д. Ядро Unix заботится о делегировании вызовов правильному коду.
теперь это шаблон стратегии, используемый в коде ядра, но вы не указали, что это должен быть пользовательский код, просто реальный пример. :-)
шаблон стратегии работает на простой идее, т. е. "предпочтение композиции над наследованием", так что стратегия/алгоритм могут быть изменены во время выполнения. Чтобы проиллюстрировать, возьмем пример, когда нам нужно шифровать различные сообщения на основе его типа, например 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();