Столкновение паттерна стратегии, паттерна стратегии и Spring

Java

Шаблон стратегии является самым простым среди шаблонов проектирования GoF 23, а также одним из наиболее часто используемых шаблонов проектирования.Сегодня мы рассмотрим шаблон стратегии.

фактический случай

Когда я был на третьем году работы, я реконструировал модуль запроса билетов маршрута путешествия Маршрут путешествия разбит на четыре случая:

  • Если туда-обратно из пункта А в пункт Б можно добраться напрямую, то проверяйте два авиабилета (туда-обратно)
  • Если прямой проезд из пункта А в пункт Б невозможен и требуется пересадка, а обратный путь возможен напрямую, то проверяйте три билета (два на выезд и один на обратный)
  • Если на выезд из точки А в точку Б можно добраться напрямую, а на обратный путь нужно сделать пересадку, то запросите три билета (один на исходящий и два на обратный)
  • Если прямого обратного пути из пункта А в пункт Б нет, то запросите четыре авиабилета (два на вылет и два на обратный)

До моего рефакторинга код выглядел так:

        int type = 1;
        // 往返都可以直达
        if (type == 1) {
            // 查询出两张机票
            return;
        }

        // 去程无法直达,需要中转,但是返程可以直达
        if (type == 2) {
            // 查询出三张机票(去程两张,返程一张)
            return;
        }
        // 去程可以直达,但是返程需要中转
        if (type == 3) {
            // 查询出三张机票(去程一张,返程两张)
            return;
        }
        // 往返都无法直达
        else{
            // 查询出四张机票(去程两张,返程两张)
            return;
        }

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

public class Ticket {
    private String desc;

    public Ticket(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "Ticket{" +
                "desc='" + desc + '\'' +
                '}';
    }
}
public interface QueryTicketService {
    List<Ticket> getTicketList();
}
public class QueryTicketAService implements QueryTicketService {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程机票"));
        list.add(new Ticket("返程机票"));
        return list;
    }
}
public class QueryTicketBService implements QueryTicketService {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程第一张机票"));
        list.add(new Ticket("去程第二张机票"));
        list.add(new Ticket("返程机票"));
        return list;
    }
}
public class QueryTicketCService implements QueryTicketService {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程机票"));
        list.add(new Ticket("返程第一张机票"));
        list.add(new Ticket("返程第二张机票"));
        return list;
    }
}
public class QueryTicketDService implements QueryTicketService {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程第一张机票"));
        list.add(new Ticket("去程第二张机票"));
        list.add(new Ticket("返程第一张机票"));
        list.add(new Ticket("返程第二张机票"));
        return list;
    }
}
public class Main {
    static Map<Integer, QueryTicketService> map = new HashMap<>();

    static {
        map.put(1, new QueryTicketAService());
        map.put(2, new QueryTicketBService());
        map.put(3, new QueryTicketCService());
        map.put(4, new QueryTicketDService());
    }

    public static void main(String[] args) {
        int type = 1;
        System.out.println(map.get(type).getTicketList());
    }
}

результат операции:

[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]

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

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

оригинальная схема стратегии

Если мы сталкиваемся с требованиями, подобными приведенным выше, первой реакцией должно быть использование оператора if else или оператора switch для выполнения разных кодов в зависимости от разных ситуаций.Это не большая проблема, но наш проект будет становиться все более и более сложным, Итак, постепенно выявились недостатки процесса: если в линию добавляется новый вид, а требуется две пересадки, то необходимо добавить несколько веток суждения (одна пересадка на исходящий рейс, две пересадки на обратный; две пересадки на , одна пересадка на обратном пути; прямая на уходящем, две пересадки на обратном и т. будет становиться все длиннее и длиннее, и поддерживать его будет все сложнее, поэтому появляется режим стратегии.

Когда в логике много операторов if else или операторов switch, и им нужно решить одну и ту же проблему, можно рассмотреть шаблон стратегии.

Самый примитивный режим стратегии имеет три роли:

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

И мой код выше имеет вкус шаблона стратегии, стратегии и ConcreteStrategy. Чего не хватает, так это контекста. Если он реализован в самом примитивном шаблоне проектирования, он фиолетовый:

public class Context {
    static Map<Integer, QueryTicketStrategy> map = new HashMap<>();

    static {
        map.put(1, new QueryTicketAConcreteStrategy());
        map.put(2, new QueryTicketBConcreteStrategy());
        map.put(3, new QueryTicketCConcreteStrategy());
        map.put(4, new QueryTicketDConcreteStrategy());
    }

    public void getTicketList(int type) {
        System.out.println(map.get(type).getTicketList());
    }
}
public class Main {
    public static void main(String[] args) {
        Context context = new Context();
        context.getTicketList(1);
    }
}

результат операции:

[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]

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

Является ли шаблон стратегии очень простым (когда я изучал шаблоны проектирования, я даже думал, что он проще, чем синглтон и простая фабрика), и особенно практичным, давайте посмотрим на UML-диаграмму шаблона стратегии:

image.png

Паттерн стратегии в JDK

Поскольку шаблон стратегии настолько практичен, есть ли какое-либо применение шаблона стратегии в JDK? Конечно, есть. Интерфейс Comparator, определенный в JDK, является практикой шаблона стратегии:

public class SortLengthComparator implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        return (o1.length() - o2.length() > 0) ? 1 : -1;
    }
}
public class Main {
    public static void main(String[] args) {
        List<String>list=new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("codebear");
        list.add("balabala");
        list.add("java");
        list.sort(new SortLengthComparator());
        System.out.println(list);
    }
}

Я определил компаратор, реализовал интерфейс Comparator, переопределил метод сравнения и реализовал функцию сравнения строк путем сравнения их длины.

результат операции:

[java, world, hello, balabala, codebear]

Интерфейс Comparator — это Strategy, а определенный мною SortLengthComparator — ConcreteStrategy.

Компаратор в сочетании с лямбдой, какую искру он даст

Определить компаратор несложно, но он недостаточно лаконичен и удобен, и нужно создать новый класс, поэтому все больше и больше людей используют Lambda для сортировки, как следующий Jiangzi:

        List<String>list=new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("codebear");
        list.add("balabala");
        list.add("java");
        List<String> newList = list.stream().sorted((a, b) -> (a.length() - b.length() > 0) ? 1 : -1).collect(Collectors.toList());
        newList.forEach(System.out::println);

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

Столкновение паттерна стратегии и Spring

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

public class Context {
    static Map<Integer, QueryTicketStrategy> map = new HashMap<>();

    static {
        map.put(1, new QueryTicketAConcreteStrategy());
        map.put(2, new QueryTicketBConcreteStrategy());
        map.put(3, new QueryTicketCConcreteStrategy());
        map.put(4, new QueryTicketDConcreteStrategy());
    }

    public void getTicketList(int type) {
        System.out.println(map.get(type).getTicketList());
    }
}

Это значит, что зависимости в классе реализации нужно поддерживать сами по себе, а магическую аннотацию @Autowired использовать нельзя, поэтому паттерн стратегии конфликтует со Spring, и паттерн стратегии нужно немного изменить, и это изменение делает стратегию выкройка проще и лучше, а также очаровательнее.

Написание 1

@Service
public class QueryTicketAConcreteStrategy implements QueryTicketStrategy {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程机票"));
        list.add(new Ticket("返程机票"));
        return list;
    }
}
@Service
public class QueryTicketDConcreteStrategy implements QueryTicketStrategy {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程第一张机票"));
        list.add(new Ticket("去程第二张机票"));
        list.add(new Ticket("返程第一张机票"));
        list.add(new Ticket("返程第二张机票"));
        return list;
    }
}
@Service
public class Context {

    @Autowired
    private QueryTicketStrategy queryTicketAConcreteStrategy;

    @Autowired
    private QueryTicketStrategy queryTicketDConcreteStrategy;

    private static Map<Integer, QueryTicketStrategy> map = new HashMap<>();

    @PostConstruct
    public void init() {
        map.put(1, queryTicketAConcreteStrategy);
        map.put(4, queryTicketAConcreteStrategy);
    }

    public void getTicketList(int type) {
        System.out.println(map.get(type).getTicketList());
    }
}
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
        run.getBean(Context.class).getTicketList(1);
    }
}

результат операции:

[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]

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

Написание 2 (я думаю, что это самое элегантное)

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

@Service("1")
public class QueryTicketAConcreteStrategy implements QueryTicketStrategy {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程机票"));
        list.add(new Ticket("返程机票"));
        return list;
    }
}
@Service("4")
public class QueryTicketDConcreteStrategy implements QueryTicketStrategy {
    @Override
    public List<Ticket> getTicketList() {
        List<Ticket> list = new ArrayList<>();
        list.add(new Ticket("去程第一张机票"));
        list.add(new Ticket("去程第二张机票"));
        list.add(new Ticket("返程第一张机票"));
        list.add(new Ticket("返程第二张机票"));
        return list;
    }
}
@Service
public class Context {

    @Autowired
    private Map<String, QueryTicketStrategy> map = new HashMap<>();


    public void getTicketList(int type) {
        String typeStr = String.valueOf(type);
        System.out.println(map.get(typeStr).getTicketList());
    }
}
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
        run.getBean(Context.class).getTicketList(1);
    }
}

результат операции:

[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]

В этом магия и очарование Spring.Он может автоматически внедрять карту, ключ — beanName, а значение — интерфейс (конкретный класс реализации).

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

END