Кусок кода шесть раз подвергался рефакторингу боссом, и мой разум рухнул

Java

StockSnap_C026SHQGNJ (1).jpg

предисловие

Привет всем, меня зовут Майло. Я вернулся 🙈

Заходите, посплетничайте за всех, а посмотрите, чем я занимаюсь сам? Кстати, недавно компания взялась за новый проект сайта по торговле сельхозпродукцией. Из-за проблемы с рефакторингом кода я чуть не подрался с начальством Я думал, что босс намеренно усложняет мне жизнь. В итоге я все-таки обнаружил, что был слишком наивен😏, вот как обстоят дела!

На еженедельном совещании начальник сообщил нам, что недавно мы получили платформу для торговли сельскохозяйственной продукцией, которая в основном используется для онлайн-торговли сельскохозяйственной продукцией в провинции. Во-первых, взять нашу провинцию Ганьсу.Мед Желтой рекиЕсли я его продам, я буду устроен продавать дыни! Ой, нет, я отвечаю за разработку функции продажи дынь🤣; скоро я разработаю следующие классы для определения дыньMelonсвоего рода:

/**
 * 瓜
 * @author Milo Lee
 * @date 2021-04-07 13:21
 */
public class Melon {
    /**品种*/
    private final String type;
    /**重量*/
    private final int weight;
    /**产地*/
    private final String origin;

    public Melon(String type, int weight, String origin) {
        this.type = type;
        this.weight = weight;
        this.origin = origin;
    }
    // getters, toString()方法省略
}

После CRUD-операции дописал бахчевые дополнения, удаления, ревизии и проверки и сдал, чтобы отпроситься 🤗.

Впервые фильтровать дыни по типу

На следующий день начальник задал мне вопрос, мол, дополнение может фильтровать дыни по типу дыни. Разве это не просто? Итак, я создалFiltersкласс, который реализуетfilterMelonByTypeметод

/**
 * @author Milo Lee
 * @date 2021-04-07 13:25
 */
public class Filters {

    /**
     * 根据类型筛选瓜类
     * @param melons 瓜类
     * @param type 类型
     * @return
     */
    public static List<Melon> filterMelonByType(List<Melon> melons, String type) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if (melon != null && type.equalsIgnoreCase(melon.getType())) {
                result.add(melon);
            }
        }
        return result;
    }
}


Хорошо, давайте проверим это

    public static void main(String[] args) {
        ArrayList<Melon> melons = new ArrayList<>();
        melons.add(new Melon("羊角蜜", 1, "泰国"));
        melons.add(new Melon("西瓜", 2, "三亚"));
        melons.add(new Melon("黄河蜜", 3, "兰州"));
        List<Melon> melonType = Filters.filterMelonByType(melons, "黄河蜜");
        melonType.forEach(melon->{
        System.out.println("瓜类型:"+melon.getType());
        });
    }

Нет проблем, покажи это начальнику, начальник посмотрел на мой код и сказал: "Если я попрошу добавить дыню в фильтр по весу, как ты это напишешь? Вернись и подумай, этот парень не будет намеренно придираешься ко мне? 😪

Дыни второго фильтра по весу

Вернувшись на свое место, я подумал про себя, в прошлый раз я уже реализовал фильтрацию дынь по типу, тогда я ему дамcopyИзменить!

Следующее:

    /**
     * 按照重量过滤瓜类
     * @param melons
     * @param weight
     * @return
     */
    public static List<Melon> filterMelonByWeight(List<Melon> melons, int weight) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if (melon != null && melon.getWeight() == weight) {
                result.add(melon);
            }
        }
        return result;
    }

public static void main(String[] args) {
     	ArrayList<Melon> melons = new ArrayList<>();
        melons.add(new Melon("羊角蜜", 1, "泰国"));
        melons.add(new Melon("西瓜", 2, "三亚"));
        melons.add(new Melon("黄河蜜", 3, "兰州"));
        List<Melon> melonType = Filters.filterMelonByType(melons, "黄河蜜");
        melonType.forEach(melon->{
            System.out.println("瓜类型:"+melon.getType());
        });

        List<Melon> melonWeight = Filters.filterMelonByWeight( melons,3);
        melonWeight.forEach(melon->{
            System.out.println("瓜重量:"+melon.getWeight());
        });
    }

Любимый способ программиста, CV понимает, ха-ха. но я нашелfilterByWeight()иfilterByType()Очень похоже, но условия фильтрации разные. Я подумал про себя, Босс не позволит мне написать список дынь по типу и весу. Возьми мой код, покажи начальству, и конечно, я боюсь того, что будет😟.

Третий фильтр Дыни по типу и весу

Чтобы справиться с задачей прохождения босса, я объединил приведенный выше код и быстро написал следующий код.

    /**
     * 按照类型和重量来筛选瓜类
     * @param melons
     * @param type
     * @param weight
     * @return
     */
    public static List<Melon> filterMelonByTypeAndWeight(List<Melon> melons, String type, int weight) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if (melon != null && type.equalsIgnoreCase(melon.getType()) && melon.getWeight() == weight) {
                result.add(melon);
            }
        }
        return result;
    }

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

ТакFiltersТаких методов будет много, а это значит, что будет написано много шаблонного кода (код избыточен, но его нужно написать);

С точки зрения нашего программиста это неприемлемо. Если вы продолжаете добавлять новые фильтры, код становится неуправляемым и подверженным ошибкам. ты иди узнайlambda表达式и函数式接口Очки знаний, а затем модифицируйте свой код. Я убедилась, он просто не может со мной поладить😤

Поведение четвертого прохода как параметр

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

На самом деле мы можем обнаружить, что каждый написанный нами метод соответствует поведению запроса, а поведение запроса должно соответствовать условию фильтра. Есть ли способ написать метод, который принимает поведение запроса в качестве параметра и возвращает наши результаты?

Затем дайте ему имя:Поведенческая параметризация, показанное на изображении ниже (слева показано, что у нас есть сейчас, справа показано, что мы хотим), вы заметили, что шаблонный код будет значительно уменьшен 🤔

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

public interface MelonPredicate {
  boolean test(Melon melon);
}

Например, фильтрацию меда из реки Хуанхэ можно записать так:HHMMelonPredicate.

public class HHMMelonPredicate implements MelonPredicate {

     @Override
     public boolean test(Melon melon) {
       return "黄河蜜".equalsIgnoreCase(melon.getType());

     }

}

По аналогии можем отфильтровать и дыни определенного веса:

public class WeightMelonPredicate implements MelonPredicate {

     @Override
     public boolean test(Melon melon) {
       return melon.getWeight() > 5000;
     }

}


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

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

В настоящее время мы абстрагируем поведение запросов, используя шаблон разработки стратегии. Нам также нужен метод для полученияMelonPredicateпараметр. Так я определилfilterMelons()метод следующим образом:

public static List<Melon> filterMelons(List<Melon> melons, MelonPredicate predicate) {
    
    List<Melon> result = new ArrayList<>();
    for (Melon melon: melons) {
      if (melon != null && predicate.test(melon)) {
        result.add(melon);
      }

    }  
    return result;
}


Готово, протестируйте, пользоваться стало намного проще, чем раньше, а потом пусть босс посмотрит

List<Melon> hhm = Filters.filterMelons(melons, new HHMMelonPredicate());

List<Melon> weight = Filters.filterMelons(melons, new WeightMelonPredicate());


В пятый раз было добавлено 100 условий фильтрации одновременно.

Как только я успокоился, начальник снова вылил на него таз с холодной водой. Он сказал, что вы просто покупаете мёд из Желтой реки на нашей платформе, если там десятки сортов дыни, я вам перечислю 100 условий фильтрации, что вам делать?

В моем сердце скачут десять тысяч травяных и грязевых коней😫! Начальник пытается поладить со мной?Хотя мой код достаточно гибкий после последней реконструкции, если вдруг добавится 100 условий фильтрации, мне все равно нужно написать 100 классов политик для реализации каждого условия фильтрации. Затем нам нужно передать стратегиюfilterMelons()метод.

Есть ли способ, который не требует создания этих классов?Умно, я быстро понял, что могу использоватьанонимный внутренний класс Java.

Следующее:

List<Melon> europeans = Filters.filterMelons(melons, new MelonPredicate() {

     @Override

     public boolean test(Melon melon) {

       return "europe".equalsIgnoreCase(melon.getOrigin());

     }

});


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

List<Melon> europeansLambda = Filters.filterMelons(
  melons, m -> "europe".equalsIgnoreCase(m.getOrigin())
);

Он выглядит намного красивее!!!, просто так я снова успешно выполнил задание. Я взволнованно взял код и позволил боссу взглянуть.

Шестое введение дженериков

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

В настоящее время нашMelonPredicateподдерживается толькоMelonсвоего рода. Что случилось с этим парнем? Может быть, однажды он захочет купить овощи и морские огурцы, что он может сделать, он не может создать для него много подобных вещей.MelonPredicateинтерфейс. В это время я вдруг вспомнил, что сказал учительДженерики, пора работать!

Итак, я определяю новый интерфейсPredicate

@FunctionalInterface
public interface Predicate<T> {

  boolean test(T t);

}


Далее мы переписываемfilterMelons()метод и переименуйте его вfilter():

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
  
	List<T> result = new ArrayList<>();

    for (T t: list) {

      if (t != null && predicate.test(t)) {

        result.add(t);

      }

    }  

    return result;

}


Теперь мы можем отфильтровать дыни следующим образом:

List<Melon> watermelons = Filters.filter(

  melons, (Melon m) -> "Watermelon".equalsIgnoreCase(m.getType()));


Точно так же мы можем сделать то же самое с числами:

List<Integer> numbers = Arrays.asList(1, 13, 15, 2, 67);

List<Integer> smallThan10 = Filters.filter(numbers, (Integer i) -> i < 10);


Вернувшись назад и проанализировав его, мы обнаружили, что с момента использованияJava 8функциональный интерфейс иlambdaПосле выражения код существенно меняется.

Я не знаю, нашли ли осторожные партнеры наши вышеперечисленныеPredicateЕще один интерфейс@FunctionalInterfaceАннотация на , это разметкафункциональный интерфейсиз.

До сих пор мы изучали концепции лямбда-выражений и функциональных интерфейсов посредством эволюции требований, а также углубляли их понимание. на самом деле знакомыйjava8друзья знают, что в нашемjava.util.functionВ комплект поставки входит более 40 таких интерфейсов.

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

Predicate<Melon> predicate = (Melon m)-> "Watermelon".equalsIgnoreCase(m.getType());

короче лямбда

lambdaВыражение состоит из трех частей, как показано на следующем рисунке:

Ниже приведеныlambdaОписание каждой части выражения:

  • слева от стрелки находитсяlambdaПараметры, используемые в теле выражения.
  • справа от стрелки находитсяlambdaпредмет.
  • стрелка простоlambdaРазделитель для параметра и тела.

этоlambdaВерсия анонимного класса выглядит следующим образом:

List<Melon> europeans = Filters.filterMelons(melons, new Predicate<Melon>() {

 @Override

 public boolean test(Melon melon) {

   return "Watermelon".equalsIgnoreCase(melon.getType());

 }

});

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

мы можем поставитьlambdaВыражение определяется какЛаконичные транзитивные анонимные функции, сначала нужно уточнитьlambdaВыражение по сути является функцией, хотя оно и не принадлежит к определенному классу, но имеет список параметров, тело функции, тип возвращаемого значения и даже может генерировать исключение, во-вторых, оно анонимно, т.е.lambdaВыражение не имеет конкретного имени функции;lambdaВыражения можно передавать как параметры, что упрощает написание кода.

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

Суммировать

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