Пример шаблона стратегии в реальном мире - PullRequest
86 голосов
/ 16 декабря 2008

Я читал о принципале OCP и о том, как использовать шаблон стратегии для достижения этой цели.

Я собирался попытаться объяснить это нескольким людям, но единственный пример, который я могу придумать, - это использовать разные классы проверки, основанные на том, какой статус имеет «заказ».

Я прочитал пару статей в Интернете, но обычно они не описывают реальную причину использования стратегии, например, создание отчетов / счетов / проверки и т. Д. *

Существуют ли примеры из реальной жизни, когда вы считаете, что шаблон стратегии является обычным явлением?

Ответы [ 16 ]

90 голосов
/ 16 декабря 2008

Что по этому поводу:

Вы должны зашифровать файл.

Для небольших файлов вы можете использовать стратегию «в памяти», когда весь файл читается и хранится в памяти (скажем, для файлов <1 ГБ) </p>

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

Это могут быть две разные стратегии для одной и той же задачи.

Код клиента будет выглядеть так же:

 File file = getFile();
 Cipher c = CipherFactory.getCipher( file.size() );
 c.performAction();



// implementations:
interface  Cipher  {
     public void performAction();
}

class InMemoryCipherStrategy implements Cipher { 
         public void performAction() {
             // load in byte[] ....
         }
}

class SwaptToDiskCipher implements Cipher { 
         public void performAction() {
             // swapt partial results to file.
         }

}

     Cipher c = CipherFactory.getCipher( file.size() );

Вернет правильный экземпляр стратегии для шифра.

Надеюсь, это поможет.

(я даже не знаю, правильное ли слово Cipher: P)

49 голосов
/ 16 января 2014

Опять старый пост, но он все равно появляется при поиске, поэтому я добавлю еще два примера (код на C #). Мне очень нравится шаблон «Стратегия», так как он много раз меня спас, когда руководители проектов говорят: «Мы хотим, чтобы приложение делало« X », но« X »еще не ясно, и оно может измениться в ближайшем будущем». " Это видео, объясняющее шаблон стратегии , использует StarCraft в качестве примера.

Вещи, которые попадают в эту категорию:

  • Сортировка: мы хотим отсортировать эти числа, но мы не знаем, будем ли мы использовать BrickSort, BubbleSort или какую-либо другую сортировку

  • Проверка: нам нужно проверить элементы согласно «Некоторому правилу», но пока не ясно, каким будет это правило, и мы можем подумать о новых.

  • Игры: мы хотим, чтобы игрок либо ходил, либо бегал, когда он двигался, но, возможно, в будущем он также сможет плавать, летать, телепортироваться, рыть под землей и т. Д.

  • Хранение информации. Мы хотим, чтобы приложение сохраняло информацию в базе данных, но позже может потребоваться сохранить файл или сделать веб-звонок

  • Вывод: нам нужно вывести X в виде простой строки, но позже это могут быть CSV, XML, JSON и т. Д.


Примеры

У меня есть проект, в котором пользователи могут назначать продукты людям в базе данных. Это присвоение продукта человеку имеет статус «Одобрено» или «Отклонено», что зависит от некоторых бизнес-правил. Например: если пользователь назначает продукт человеку определенного возраста, его статус должен быть отклонен; Если разница между двумя полями в элементе больше 50, его статус отклоняется и т. Д.

Сейчас, на момент разработки, эти бизнес-правила еще не полностью ясны, и новые правила могут появиться в любое время. Сила stragety-pattern заключается в том, что я создал RuleAgent, которому предоставляется список IRules.

public interface IRule {
    bool IsApproved(Assignment assignment); 
 }

В момент назначения продукта человеку я создаю RuleAgent, даю ему список правил (все они реализуют IRule) и прошу его подтвердить назначение. Он пройдет через все свои правила. Что, поскольку все они реализуют один и тот же интерфейс, все имеют метод IsApproved и возвращают false, если любой из них возвращает false.

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

public OvertimeRule : IRule
{
    public bool IsApproved(Assignment assignment) //Interface method
    {
        if (assignment.Person.Timesheet >= 40)
        {
            return false;
        }
        return true;
    }
}

public InternRule : IRule
{
    public bool IsApproved(Assignment assignment) //Interface method
    {
        if (assignment.Person.Title == "Intern")
        {
            return false;
        }
        return true;
    }
}

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


Еще один замечательный пример: видеосериал Скотта Аллена по адресу http://www.asp.net/mvc/pluralsight, где он использует шаблон стратегии в модульной части приложения

Он создает веб-сайт, на котором есть страница, отображающая элементы, основанные на популярности. Однако «Популярным» может быть много вещей (большинство просмотров, большинство подписчиков, дата создания, большая активность, наименьшее количество комментариев и т. Д.), И в случае, если руководство еще не знает точно, как сделать заказ, и может захотеть поэкспериментировать с различными заказы на более поздний срок. Вы создаете интерфейс (IOrderAlgorithm или что-то еще) с методом заказа, и позволяете объекту Orderer делегировать порядок конкретной реализации интерфейса IOrderAlgorithm. Вы можете создать «CommentOrderer», «ActivityOrderer» и т. Д. И просто отключать их при появлении новых требований.

12 голосов
/ 09 января 2010

Я могу вспомнить несколько довольно простых примеров:

  • Сортировка списка. Стратегия - это сравнение, используемое для определения того, какой из двух элементов в списке является «первым»
  • У вас может быть приложение, в котором сам алгоритм сортировки (QuickSort, HeapSort и т. Д.) Может быть выбран во время выполнения
  • Добавления, макеты и фильтры в Log4Net и Log4j
  • Менеджеры макетов в наборах инструментов пользовательского интерфейса
  • Сжатие данных. У вас может быть интерфейс ICompressor, единственный метод которого выглядит примерно так:

    byte [] compress (byte [] input);

    Ваши конкретные классы сжатия могут быть такими, как RunLengthCompression, DeflateCompression и т. Д.

10 голосов
/ 03 февраля 2016

Ключевые примечания:

  1. Стратегия - это модель поведенческого проектирования. Он используется для переключения между семейством алгоритмов.

  2. Этот шаблон содержит одну абстрактную стратегию интерфейс и множество конкретных реализаций стратегии ( алгоритмы ) этого интерфейса.

  3. Приложение использует стратегию interface only. В зависимости от некоторых параметров конфигурации конкретная стратегия будет помечена как interface .

Диаграмма UML из Википедии

enter image description here

Пример одного реального слова: Авиакомпании, предоставляющие скидки в течение нескольких месяцев (июль-декабрь) . У вас может быть один Fare модуль, который определяет варианты цены в зависимости от номера месяца.

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

import java.util.*;

/* Interface for Strategy */
interface OfferStrategy {
    public String getName();
    public double getDiscountPercentage();
}
/* Concrete implementation of base Strategy */
class NoDiscountStrategy implements OfferStrategy{
    public String getName(){
        return this.getClass().getName();
    }
    public double getDiscountPercentage(){
        return 0;
    }
}
/* Concrete implementation of base Strategy */
class QuarterDiscountStrategy implements OfferStrategy{
    public String getName(){
        return this.getClass().getName();
    }
    public double getDiscountPercentage(){
        return 0.25;
    }
}
/* Context is optional. But if it is present, it acts as single point of contact
   for client. 

   Multiple uses of Context
   1. It can populate data to execute an operation of strategy
   2. It can take independent decision on Strategy creation. 
   3. In absence of Context, client should be aware of concrete strategies. Context acts a wrapper and hides internals
   4. Code re-factoring will become easy
*/
class StrategyContext {
    double price; // price for some item or air ticket etc.
    Map<String,OfferStrategy> strategyContext = new HashMap<String,OfferStrategy>();
    StrategyContext(double price){
        this.price= price;
        strategyContext.put(NoDiscountStrategy.class.getName(),new NoDiscountStrategy());
        strategyContext.put(QuarterDiscountStrategy.class.getName(),new QuarterDiscountStrategy());        
    }
    public void applyStrategy(OfferStrategy strategy){
        /* 
        Currently applyStrategy has simple implementation. You can use Context for populating some more information,
        which is required to call a particular operation            
        */
        System.out.println("Price before offer :"+price);
        double finalPrice = price - (price*strategy.getDiscountPercentage());
        System.out.println("Price after offer:"+finalPrice);
    }
    public OfferStrategy getStrategy(int monthNo){
        /*
            In absence of this Context method, client has to import relevant concrete Strategies everywhere.
            Context acts as single point of contact for the Client to get relevant Strategy
        */
        if ( monthNo < 6 )  {
            return strategyContext.get(NoDiscountStrategy.class.getName());
        }else{
            return strategyContext.get(QuarterDiscountStrategy.class.getName());
        }

    }
}
public class StrategyDemo{    
    public static void main(String args[]){
        StrategyContext context = new StrategyContext(100);
        System.out.println("Enter month number between 1 and 12");
        int month = Integer.parseInt(args[0]);
        System.out.println("Month ="+month);
        OfferStrategy strategy = context.getStrategy(month);
        context.applyStrategy(strategy);
    }

}

выход:

Enter month number between 1 and 12
Month =1
Price before offer :100.0
Price after offer:100.0

Enter month number between 1 and 12
Month =7
Price before offer :100.0
Price after offer:75.0

Полезные статьи:

стратегия паттерн по dzone

стратегия паттерн от sourcemaking

9 голосов
/ 11 января 2010

Одним из распространенных применений шаблона стратегии является определение пользовательских стратегий сортировки (на языках без функций высшего порядка), например отсортировать список строк по длине в Java, передав анонимный внутренний класс (реализация интерфейса стратегии):

List<String> names = Arrays.asList("Anne", "Joe", "Harry");
Collections.sort(names, new Comparator<String>() {
  public int compare(String o1, String o2) {
    return o1.length() - o2.length();
  }
});
Assert.assertEquals(Arrays.asList("Joe", "Anne", "Harry"), names);

Аналогичным образом стратегии могут использоваться для собственных запросов с объектными базами данных, например в db4o:

List<Document> set = db.query(new Predicate<Document>() {
  public boolean match(Document candidate) {
    return candidate.getSource().contains(source);
  }
});
8 голосов
/ 16 декабря 2008

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

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

Я не знаю, насколько это распространено, но я чувствовал, что он идеально подходит для модели стратегии.

7 голосов
/ 05 октября 2012

Я знаю, что это старый вопрос, но я думаю, что у меня есть еще один интересный пример, который я недавно реализовал.

Это очень практичный пример использования стратегии в системе доставки документов.

У меня была система доставки PDF, которая получила архив, содержащий много документов и некоторые метаданные. На основе метаданных было решено, куда поместить документ; скажем, в зависимости от данных, я мог бы хранить документ в A, B или C системах хранения, или в комбинации из трех.

Различные клиенты использовали эту систему, и у них были разные требования к откату / обработке ошибок в случае ошибок: один хотел, чтобы система доставки остановилась при первой ошибке, оставила все документы уже доставленными в свои хранилища, но остановила процесс, а не доставить что-нибудь еще; другой хотел откатить с B в случае ошибок при сохранении в C, но оставить все, что уже было доставлено на A. Легко представить, что у третьего или четвертого тоже будут разные потребности.

Чтобы решить эту проблему, я создал базовый класс доставки, который содержит логику доставки, а также методы для отката вещей из всех хранилищ. Эти методы фактически не вызываются системой доставки напрямую в случае ошибок. Вместо этого класс использует Dependency Injection для получения класса «Стратегия отката / обработки ошибок» (на основе клиента, использующего систему), который вызывается в случае ошибок, который, в свою очередь, вызывает методы отката, если это подходит для этой стратегии.

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

rollbackStrategy.reportSuccessA(...);
rollbackStrategy.reportFailureB(...);

if (rollbackStrategy.mustAbort()) {
    rollbackStrategy.rollback(); // rollback whatever is needed based on reports
    return false;
}

Итак, теперь у меня есть две разные стратегии: одна - это QuitterStrategy (которая завершается при первой ошибке и ничего не очищает), а другая - MaximizeDeliveryToAStrategy (которая старается изо всех сил не прерывать процесс и никогда не откатывать вещи, доставленные в хранилище A, но откатывает вещи с B, если доставка до C не удалась).

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

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

После небольшого исследования кажется, что это «составной шаблон» (например, MVC, шаблон, который использует несколько шаблонов проектирования под определенным образом), называемый Advisor . Это советник о том, должна ли доставка продолжаться или нет, но он также является активным обработчиком ошибок, так как он может откатывать вещи по запросу.

В любом случае, это довольно сложный пример, который может создать у вас ощущение, что использование шаблона стратегии слишком просто / глупо. Он может быть действительно сложным и даже более применимым при использовании вместе с другими шаблонами.

6 голосов
/ 08 июля 2015

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

Позвольте мне объяснить на простом практическом примере

enum Speed {
  SLOW, MEDIUM, FAST;
}

class Sorter {
 public void sort(int[] input, Speed speed) {
    SortStrategy strategy = null;
    switch (speed) {
    case SLOW:
        strategy = new SlowBubbleSortStrategy();
        break;
    case MEDIUM:
        strategy = new MediumInsertationSortStrategy();
        break;

    case FAST:
        strategy = new FastQuickSortStrategy();
        break;
    default:
        strategy = new MediumInsertationSortStrategy();
    }
    strategy.sort(input);
 }

}

interface SortStrategy {

    public void sort(int[] input);
}

class SlowBubbleSortStrategy implements SortStrategy {

   public void sort(int[] input) {
    for (int i = 0; i < input.length; i++) {
        for (int j = i + 1; j < input.length; j++) {
            if (input[i] > input[j]) {
                int tmp = input[i];
                input[i] = input[j];
                input[j] = tmp;
            }
        }
    }
    System.out.println("Slow sorting is done and the result is :");
    for (int i : input) {
        System.out.print(i + ",");
    }
  }

 }

class MediumInsertationSortStrategy implements SortStrategy {

public void sort(int[] input) {
    for (int i = 0; i < input.length - 1; i++) {
        int k = i + 1;
        int nxtVal = input[k];
        while (input[k - 1] > nxtVal) {
            input[k] = input[k - 1];
            k--;
            if (k == 0)
                break;
        }
        input[k] = nxtVal;
    }
    System.out.println("Medium sorting is done and the result is :");
    for (int i : input) {
        System.out.print(i + ",");
    }

 }

}

class FastQuickSortStrategy implements SortStrategy {

public void sort(int[] input) {
    sort(input, 0, input.length-1);
    System.out.println("Fast sorting is done and the result is :");
    for (int i : input) {
        System.out.print(i + ",");
    }
}

private void sort(int[] input, int startIndx, int endIndx) {
    int endIndexOrig = endIndx;
    int startIndexOrig = startIndx;
    if( startIndx >= endIndx)
        return;
    int pavitVal = input[endIndx];
    while (startIndx <= endIndx) {
        while (input[startIndx] < pavitVal)
            startIndx++;
        while (input[endIndx] > pavitVal)
            endIndx--;
        if( startIndx <= endIndx){
            int tmp = input[startIndx];
            input[startIndx] = input[endIndx];
            input[endIndx] = tmp;
            startIndx++;
            endIndx--;
        }
    }
    sort(input, startIndexOrig, endIndx);
    sort(input, startIndx, endIndexOrig);
 }

}  

Тестовый код для этого

public class StrategyPattern {
  public static void main(String[] args) {
    Sorter sorter = new Sorter();
    int[] input = new int[] {7,1,23,22,22,11,0,21,1,2,334,45,6,11,2};
    System.out.print("Input is : ");
    for (int i : input) {
        System.out.print(i + ",");
    }
    System.out.println();
    sorter.sort(input, Speed.SLOW);
 }

}

Тот же пример взят из http://coder2design.com/strategy-pattern/

4 голосов
/ 15 января 2015

Хорошим примером модели стратегии может служить игра, в которой у нас могут быть разные персонажи, и каждый персонаж может иметь несколько видов оружия для атаки, но одновременно может использовать только одно оружие. Таким образом, у нас есть персонаж в качестве контекста, например, King, Commander, Knight, Soldier и оружие как стратегия, где attack () может быть методом / алгоритмом, который зависит от используемого оружия. Так что, если бы конкретными классами оружия были Sword, Axe, Crossbow, BowAndArrow и т. Д., То все они реализовали бы метод attack (). Я уверен, что дальнейшие объяснения не нужны.

2 голосов
/ 18 января 2014

Допустим, вы хотите написать алгоритм для вычисления n-го дня X данного месяца и года, например, второго понедельника октября 2014 года. Вы хотите использовать класс времени Android android.text.format.Time для представления дата, но вы также хотите написать общий алгоритм, который также может применяться к java.util.Calendar.

Это то, что я сделал.

In DatetimeMath.java:

public interface DatetimeMath { 
    public Object createDatetime(int year, int month, int day);

    public int getDayOfWeek(Object datetime);

    public void increment(Object datetime);
}

In TimeMath.java:

public class TimeMath implements DatetimeMath {
    @Override
    public Object createDatetime(int year, int month, int day) {
        Time t = new Time();
        t.set(day, month, year);
        t.normalize(false);
        return t;
    }

    @Override
    public int getDayOfWeek(Object o) {
        Time t = (Time)o;
        return t.weekDay;
    }   

    @Override
    public void increment(Object o) {
        Time t = (Time)o;
        t.set(t.monthDay + 1, t.month, t.year);
        t.normalize(false);
    }
}

В OrdinalDayOfWeekCalculator.java, класс с универсальным алгоритмом:

public class OrdinalDayOfWeekCalculator {   
    private DatetimeMath datetimeMath;

    public OrdinalDayOfWeekCalculator(DatetimeMath m) {
        datetimeMath = m;
    }

    public Object getDate(int year, int month, int dayOfWeek, int ordinal) {
        Object datetime = datetimeMath.createDatetime(year, month, 1);
        if (datetimeMath.getDayOfWeek(datetime) == dayOfWeek) {
            return datetime;
        } 
        int xDayCount = 0;
        while (xDayCount != ordinal) {
            datetimeMath.increment(datetime);
            if (datetimeMath.getDayOfWeek(datetime) == dayOfWeek) {
                xDayCount++;
            }
        }
        return datetime;
    }
}

В моем приложении для Android я бы назвал что-то вроде

OrdinalDayOfWeekCalculator odowc = 
        new OrdinalDayOfWeekCalculator(new TimeMath());
Time canadianThanksgiving = (Time)odowc.getDate(
        year, Calendar.OCTOBER, Time.MONDAY, 2);

Если я хочу повторно использовать тот же алгоритм для java.util.Calendar, я просто написал бы класс CalendarMath, который реализует три метода в DatetimeMath, а затем использовал бы

OrdinalDayOfWeekCalculator odowc2 = 
        new OrdinalDayOfWeekCalculator(new CalendarMath());
Calendar canadianThanksgivingCal = (Calendar)odowc2.getDate(
        year, Calendar.OCTOBER, Calendar.MONDAY, 2);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...