Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

воскресенье, 4 марта 2018 г.

Java 8 и паттерн Стратегия / Хабрахабр / Программирование на Java

Потенциально возможное продолжение книги Паттерны проектирования (Элизабет Фримен и другие).

На дворе 2017 год. В компанию, где работает старший разработчик Джо, пришел на стажировку молодой студент Мартин. Он целый год скрупулезно изучал Java по современному учебнику с акцентом на функциональные интерфейсы, лямбда-выражения и прочие новшества.

Джо поинтересовался, знает ли Мартин паттерны проектирования. Мартин не знал, совсем не знал. И Джо понял, как он будет стажировать Мартина. Он даст ему те задачи, которые, по его мнению, без паттернов проектирования будут иметь трудно поддерживаемые решения. Когда Мартин напишет приложение, Джо сначала помучает его с расширением архитектуры, а затем покажет ему, как легко можно ее расширять, если при проектировании изначально заложить в нее чудесные паттерны. Однако, он решил сразу предупредить Мартина о направлениях расширения: а вдруг он сам сможет нечаянно реализовать паттерны проектирования, не зная их? Молодые люди весьма изобретательны.

Джо: Слушай, Мартин, для тебя есть ответственная работа. Нужно написать прототип симулятора утиного пруда. Создай модели уток, они могут крякать, летать, плавать и делать прочие действия. Вместо действий пока используй заглушки, выводящие в консоль текстовые сообщения. Учти, что утки могут быть разных видов, в том числе резиновая утка, деревянная, газетная, ручная утки, утки с утятами. Значит, не все разновидности уток могут в принципе крякать или летать или плавать или совершать другие действия. Некоторые утки могут со временем приобретать новые возможности, или утрачивать их. Учти, что тебе это приложение придется поддерживать длительное время, а начальство будет придумывать всё новые виды уток.

Мартин молча принялся за работу.

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

Runnable fly = () -> System.out.println("Я летаю");

И затем выполнить его так:

fly.run();

Раз мы сохранили поведение в переменной, то его всегда можно будет заменить на любое другое поведение, даже динамически во время жизни объекта, не говоря уже о наследовании. А раз количество поведений также может меняться, то можно не заводить под каждое действие свою переменную, а сохранять их в динамически изменяемой структуре данных. Например, в Set. Или, лучше, в Map<T,U>, указывая в качестве ключа текстовый идентификатор поведения, а то мы это поведение потом не сможем отличить от других. Наверное, стоит для хранения и манипуляции поведений создать свой собственный класс и его объект встроить в поле базового класса уток. С него и начнем:

package patterns.and.lambdas.ducks;

import java.util.concurrent.ConcurrentSkipListMap;


public class BehaviorRegistry<T> {
    public ConcurrentSkipListMap<String, T> map = new ConcurrentSkipListMap<>();

    public void add(final String behaveName, final T behaveFunc) {
        this.assertContainsNameNot(behaveName);
        BehaviorRegistry.assertArgNotNull(behaveFunc);
        this.map.put(behaveName, behaveFunc);
    }
            
    public boolean contains(final String behaveName) {
        BehaviorRegistry.assertArgNotNull(behaveName);
        this.assertMapNotNull();
        return this.map.containsKey(behaveName) && (this.map.get(behaveName) != null);
    }
    
    public T get(final String behaveName) {
        this.assertContainsName(behaveName);
        return this.map.get(behaveName);
    }
       
    public void replace(final String behaveName, final T behaveFunc) {
        this.assertContainsName(behaveName);
        BehaviorRegistry.assertArgNotNull(behaveFunc);
        this.map.put(behaveName, behaveFunc);
    }
    
    public void remove(final String behaveName) {
        this.assertContainsName(behaveName);
        this.map.remove(behaveName);
    }
    
    protected static void assertArgNotNull(final Object arg) {
        if ((arg instanceof String) && !"".equals(arg)) return;
        if (arg != null) return;
        throw new RuntimeException("Пустой аргумент.");
    }
    
    protected void assertContainsName(final String behaveName) {
        BehaviorRegistry.assertArgNotNull(behaveName);
        this.assertMapNotNull();
        if (!this.contains(behaveName)) {
            throw new RuntimeException("Способность \"" + behaveName + "\" не зарегистрирована.");
        }
    }
    
    protected void assertContainsNameNot(final String behaveName) {
        BehaviorRegistry.assertArgNotNull(behaveName);
        this.assertMapNotNull();
        if (this.contains(behaveName)) {
            throw new RuntimeException("Способность \"" + behaveName + "\" уже зарегистрирована.");
        }
    }
    
    protected void assertMapNotNull() {
        if (this.map == null) throw new RuntimeException("Отсутствует map.");
    }
}

Метод запуска способности мы в классе BehaviorRegistry реализовывать не будем, так как этот класс обобщен, поэтому мы не знаем, какой функциональный интерфейс лежит в основе его конкретного экземпляра, а значит не знаем название исполняющей функции: run(), call(), accept(), test(), apply() и т.д., и не знаем количество и типы аргументов для этих функций, и что эти функции возвращают.

Теперь воспользуемся им в классе Duck:

package patterns.and.lambdas.ducks;

public class Duck {
    protected BehaviorRegistry<Runnable> behaviors = new BehaviorRegistry<>();
    
    public void perform(final String behaveName) {
        this.behaviors.get(behaveName).run();
    }
    
    
    public void performAll() {
        this.behaviors.map.descendingKeySet().forEach(this::perform);
        System.out.println("----------------------------------------------");
    }
}</nobr>

В принципе, это всё. Даже наследование и иерархия классов не понадобилась. Просто будем создавать объекты Duck и в поле behaviors сохранять столько разных вариантов поведения, сколько нужно, и затем исполнять их когда нужно. Вот такая получилась архитектура:



На диаграмме нет ни одного прямоугольника ни для какого поведения, так как в данной архитектуре поведение утки является таким же анонимным значением, как и число 6 является анонимным значением для переменной int number. Архитектуре не важно, что делает и как устроено поведение, лишь бы оно удовлетворяло функциональному интерфейсу Runnable, как и переменной int number не важно, какое число в него сохраняют, лишь бы оно было целочисленным.

Эксплуатировать архитектуру будет проще, если заранее заготовить справочник некоторых поведений в виде enum:

package patterns.and.lambdas.ducks;

import java.util.function.BiConsumer;


public enum EBehaviors {
    
    Display("представиться", () -> System.out.println("Я утка")),
    Fly("летать", () -> System.out.println("Я летаю")),
    Sleep("спать", () -> System.out.println("Z-z-z-z")),
    Quack("крякать", () -> System.out.println("Кря-кря-кря")),
    Propagate("размножаться", () -> System.out.println("O_o"));
    
    public String   name;
    public Runnable func;
    
    private EBehaviors(final String m_name, final Runnable m_func) {
        this.name = m_name;
        this.func = m_func;
    }
    
    public void sendTo(final BiConsumer<String, Runnable> someApiFunction) {
        someApiFunction.accept(this.name, this.func);
    }
    
    public void sendTo(final BiConsumer<String, Runnable> someApiFunction, final Runnable m_func) {
        someApiFunction.accept(this.name, m_func);
    }
}

Теперь можно смело начинать конкретную эксплуатацию:

package patterns.and.lambdas.ducks;

import java.util.stream.Stream;

import patterns.and.lambdas.ducks.Duck;
import patterns.and.lambdas.ducks.EBehaviors;

public class Test {
    public static void main(final String[] args) {
        Runnable behaviorFunc = null;
        
        final Duck mallardDuck = new Duck();
        behaviorFunc = () -> System.out.println("Я кряква");
        EBehaviors.Display.sendTo(mallardDuck.behaviors::add, behaviorFunc);
        EBehaviors.Fly.sendTo(mallardDuck.behaviors::add);
        EBehaviors.Quack.sendTo(mallardDuck.behaviors::add);
        
        final Duck redheadDuck = new Duck();
        behaviorFunc = () -> System.out.println("Я красноголовая утка");
        EBehaviors.Display.sendTo(redheadDuck.behaviors::add, behaviorFunc);
        EBehaviors.Fly.sendTo(redheadDuck.behaviors::add);
        EBehaviors.Quack.sendTo(redheadDuck.behaviors::add);
        
        final Duck rubberDuck = new Duck();
        behaviorFunc = () -> System.out.println("Я резиновая утка");
        EBehaviors.Display.sendTo(rubberDuck.behaviors::add, behaviorFunc);
        EBehaviors.Quack.sendTo(rubberDuck.behaviors::add);
        
        final Duck decoyDuck = new Duck();
        behaviorFunc = () -> System.out.println("Я деревянная утка");
        EBehaviors.Display.sendTo(decoyDuck.behaviors::add, behaviorFunc);
        
        final Duck exclusiveDuck = new Duck();
        behaviorFunc = () -> System.out.println("Я эксклюзивная утка");
        EBehaviors.Display.sendTo(exclusiveDuck.behaviors::add, behaviorFunc);
        behaviorFunc = () -> System.out.println("Я изрыгаю пламя    <== эксклюзивное поведение");
        exclusiveDuck.behaviors.add("палить огнем", behaviorFunc);
        
        final Duck[] ducks = { mallardDuck, redheadDuck, rubberDuck, decoyDuck, exclusiveDuck };
        
        
        System.out.println("############################################## 1");
        Stream.of(ducks).forEachOrdered(Duck::performAll);
        
        
        System.out.println("############################################## 2");
        behaviorFunc = () -> System.out.println("Кряяааааааа!    <== подменили в RunTime");
        EBehaviors.Display.sendTo(redheadDuck.behaviors::replace, behaviorFunc);
        redheadDuck.performAll();
        
        
        System.out.println("############################################## 3");
        EBehaviors.Propagate.sendTo(mallardDuck.behaviors::add);
        EBehaviors.Sleep.sendTo(mallardDuck.behaviors::add);
        mallardDuck.behaviors.map.forEach((name, func) -> {
            final Runnable newFunc = () -> {
                func.run();
                System.out.println("   ^^^^^  последействие");
            };
            mallardDuck.behaviors.replace(name, newFunc);
        });
        mallardDuck.performAll();
        
        
        System.out.println("############################################## 4");
        for (final Duck duck : ducks) {
            Stream.of(EBehaviors.values()).map(val -> val.name).filter(duck.behaviors::contains)
                    .forEach(duck.behaviors::remove);
        }
        Stream.of(ducks).forEachOrdered(Duck::performAll);
    }
}

И вот результат:

############################################## 1
Я кряква
Я летаю
Кря-кря-кря
----------------------------------------------
Я красноголовая утка
Я летаю
Кря-кря-кря
----------------------------------------------
Я резиновая утка
Кря-кря-кря
----------------------------------------------
Я деревянная утка
----------------------------------------------
Я эксклюзивная утка
Я изрыгаю пламя    <== эксклюзивное поведение
----------------------------------------------
############################################## 2
Кряяааааааа!    <== подменили в RunTime
Я летаю
Кря-кря-кря
----------------------------------------------
############################################## 3
Z-z-z-z
   ^^^^^  последействие
O_o
   ^^^^^  последействие
Я кряква
   ^^^^^  последействие
Я летаю
   ^^^^^  последействие
Кря-кря-кря
   ^^^^^  последействие
----------------------------------------------
############################################## 4
----------------------------------------------
----------------------------------------------
----------------------------------------------
----------------------------------------------
Я изрыгаю пламя    <== эксклюзивное поведение
----------------------------------------------


Мартин сдает работу Джо…

Джо: Это что? Разве это архитектура приложения? Всего-лишь два прямоугольника с одной связью на диаграмме. Когда я сделал свою версию программы, на моей диаграмме было двенадцать прямоугольников в самом начале, когда утки могли только крякать и летать, и их стало более трехсот через полгода, когда начальство придумало пятьдесят различных аспектов поведения, и для каждого аспекта по несколько различных вариантов реализаций! Я отделил изменяющееся от постоянного, и для каждой группы в архитектуре создал по своей иерархии, чтобы добиться гибкости и устранить дублирование кода. А у тебя, я смотрю, изменяющееся вообще вынесено за пределы архитектуры, в область анонимного и неопределенного, ты оставил в ней только постоянное. Однако, твое приложение по крайней мере не менее гибкое и расширяемое, чем мое. И дублирования кода я шибко не вижу, разве что при создании кряквы и красноголовой утки одинаковое добавление способностей крякать и летать можно было вынести в отдельный метод, но это мелочи. Я думаю, ты будешь очень быстро расширять это приложение, поэтому у меня есть для тебя еще работенка для нагрузки. Как насчет создания мониторов погоды для метеостанции? Я только что получил техтребования.

Источник: https://habrahabr.ru/post/350386/?utm_source=habrahabr&utm_medium=rss&utm_campaign=350386

Смотри также:

Зачем нужна Java. http://fetisovvs.blogspot.com/2014/07/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html

Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html

Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html

Комментариев нет:

Отправить комментарий