Java 8 and Strategy pattern

Potentially possible continuation of the book Design Patterns (Elizabeth Freeman and others) .

In the yard 2017. A young student, Martin, came to an internship with a senior developer Joe. He spent the whole year scrupulously studying Java using a modern textbook with an emphasis on functional interfaces, lambda expressions, and other innovations.

Joe wondered if Martin knew design patterns. Martin did not know, did not know at all. And Joe realized how he would be training Martin. He will give him those tasks which, in his opinion, would have difficult to maintain solutions without design patterns. When Martin writes the application, Joe first torments him with the expansion of the architecture, and then shows him how easy it is to expand it, if you design it from the beginning to put wonderful patterns in it. However, he decided to immediately warn Martin about the directions of expansion: what if he himself could inadvertently implement design patterns without knowing them? Young people are very inventive.

Joe:Listen, Martin, there is a responsible job for you. You need to write a prototype of a duck pond simulator. Create duck models, they can quack, fly, swim and do other things. Instead of actions, use stubs that display text messages to the console. Bear in mind that ducks can be of various types, including rubber duck, wooden, newspaper, hand duck, duck with ducklings. So, not all varieties of ducks can in principle quack or fly or swim or perform other actions. Some ducks may eventually acquire new opportunities, or lose them. Bear in mind that you will have to support this application for a long time, and the authorities will come up with new types of ducks.

Martin silently set to work.

Hm. There are many different types of ducks. Ducks of different species have different skill sets. These sets can dynamically change over time, decrease or increase in quantity, and are replaced by other ones of the same type. It would be great to keep duck skills, that is, behavior, that is, functions, in some variables. Lambda expressions can help with this. For example, you can save the behavior like this:

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

And then execute it like this:

fly.run();

Once we have saved the behavior in a variable, it can always be replaced with any other behavior, even dynamically during the lifetime of an object, not to mention inheritance. And since the number of behaviors can also change, you can not start your own variable for each action, but save them in a dynamically changing data structure. For example, in Set. Or better, in Map, specifying the text identifier of the behavior as the key, otherwise we will not be able to distinguish this behavior from others. It is probably worthwhile to create your own class for storing and manipulating behaviors and embed its object in the field of the base class of ducks. Let's start with it:

package patterns.and.lambdas.ducks;
import java.util.concurrent.ConcurrentSkipListMap;
/**
 * Динамически изменяемый реестр способностей.
 * 
 * @param 
 *            Любой функциональный интерфейс: Runnable, Callable, Supplier, BooleanSupplier,
 *            Consumer, BiConsumer, Predicate, BiPredicate, Function,
 *            BiFunction, UnaryOperator, BinaryOperator, другой из пакета
 *            {@link java.util.function} или свой собственный или любой другой функциональный.
 */
public class BehaviorRegistry {
    public ConcurrentSkipListMap 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.");
    }
}

We will not implement the ability launch method in the BehaviorRegistry class, since this class is generalized, therefore we do not know which functional interface underlies its specific instance, and therefore we do not know the name of the executing function: run (), call (), accept ( ), test (), apply (), etc., and we don’t know the number and types of arguments for these functions, and what these functions return.

Now use it in the Duck class:

package patterns.and.lambdas.ducks;
public class Duck {
    protected BehaviorRegistry 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("----------------------------------------------");
    }
}

In principle, that’s all. Even inheritance and class hierarchy were not needed. We will just create Duck objects and save as many different behaviors as needed in the behaviors field, and then execute them when necessary. This is how the architecture turned out:



There is not a single rectangle on the diagram for any behavior, since in this architecture the behavior of the duck is the same anonymous value as the number 6 is the anonymous value for the variable int number. The architecture does not matter what the behavior does and how it is arranged, if it satisfies the Runnable functional interface, just as the int number variable does not matter what number is stored in it, if only it is integer.

It will be easier to operate the architecture if you pre-prepare a directory of some behaviors in the form of 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 someApiFunction) {
        someApiFunction.accept(this.name, this.func);
    }
    public void sendTo(final BiConsumer someApiFunction, final Runnable m_func) {
        someApiFunction.accept(this.name, m_func);
    }
}

Now you can safely begin a specific operation:

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);
    }
}

And here is the result:

############################################## 1
I'm mallard
I fly
Quack quack quack
----------------------------------------------
I'm a red-headed duck
I fly
Quack quack quack
----------------------------------------------
I'm a rubber duck
Quack quack quack
----------------------------------------------
I'm a wooden duck
----------------------------------------------
I'm an exclusive duck
I spew flame <== exclusive behavior
----------------------------------------------
################################################### 2
Kryayaaaaaaaaa! <== changed in RunTime
I fly
Quack quack quack
----------------------------------------------
################################################### 3
Zzzz
   ^^^^^ aftereffect
O_o
   ^^^^^ aftereffect
I'm mallard
   ^^^^^ aftereffect
I fly
   ^^^^^ aftereffect
Quack quack quack
   ^^^^^ aftereffect
----------------------------------------------
############################################## 4
----------------------------------------------
----------------------------------------------
----------------------------------------------
----------------------------------------------
I spew flame <== exclusive behavior
----------------------------------------------


Martin hands over Joe's work ...

Joe:What's this? Is this application architecture? Just two rectangles with one link in the diagram. When I made my version of the program, there were twelve rectangles on the diagram at the very beginning, when the ducks could only quack and fly, and there were more than three hundred in six months, when the authorities came up with fifty different aspects of behavior, and for each aspect several different implementation options ! I separated the variable from the permanent, and for each group in the architecture I created according to my hierarchy in order to achieve flexibility and eliminate code duplication. And you, I see, the changing is generally taken outside the boundaries of architecture, into the realm of anonymous and indefinite, you left only the constant in it. However, your application is at least as flexible and extensible as mine. And I don’t see much duplication of code, except that when creating a mallard and a red-headed duck, the same addition of the ability to quack and fly could be carried out in a separate method, but these are trifles. I think you will expand this application very quickly, so I still have some work for you to do with the load. How about creating weather monitors for a weather station? I just got the technical requirements.


Also popular now: