📜 ⬆️ ⬇️

Java 8 and Strategy pattern

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

In the yard in 2017. A young student, Martin, joined the company where senior developer Joe works. For a whole year, he carefully studied Java using a modern textbook with an emphasis on functional interfaces, lambda expressions and other innovations.

Joe wondered if Martin knows the design patterns. Martin did not know, did not know at all. And Joe understood how he would be practicing Martin. He will give him the tasks that, in his opinion, without design patterns, will have difficult-to-support solutions. When Martin writes the application, Joe will first torment him with the expansion of the architecture, and then show him how easy it is to expand it, if the design is initially based on wonderful patterns. However, he decided to immediately warn Martin about the directions of expansion: what if he himself could accidentally implement design patterns without knowing them? Young people are very creative.

Joe: Listen, Martin, you have a responsible job. You need to write a prototype of a duck pond simulator. Create models of ducks, they can quack, fly, swim and do other activities. Instead of actions, use stubs that display text messages to the console. Bear in mind that ducks can be of different types, including rubber ducks, wooden, newspaper, hand ducks, ducks with ducklings. This means that not all species of ducks can, in principle, quack or fly or swim or perform other actions. Some ducks may eventually acquire new features, or lose them. Consider that you have to maintain this application for a long time, and the authorities will invent all 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, be replaced with other ones of the same type. It would be great to keep duck skills, that is, behavior, that is, functions, in some variables. This can help lambda expressions. For example, you can save the behavior as follows:

Runnable fly = () -> System.out.println(" "); 

And then do it like this:

 fly.run(); 

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

 package patterns.and.lambdas.ducks; import java.util.concurrent.ConcurrentSkipListMap; /** *    . * * @param <T> *   : Runnable, Callable<V>, Supplier<T>, BooleanSupplier, * Consumer<T>, BiConsumer<T,U>, Predicate<T>, BiPredicate<T,U>, Function<T,R>, * BiFunction<T,U,R>, UnaryOperator<T>, BinaryOperator<T>,    * {@link java.util.function}       . */ 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."); } } 

We will not implement the ability launch method in the BehaviorRegistry class, since this class is generalized, so we do not know which functional interface underlies its specific instance, which means we don’t know the name of the performing function: run (), call (), accept ( ), test (), apply (), etc., and do not 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<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> 

Basically, that's all. Even inheritance and class hierarchy is not needed. We will simply create Duck objects and, in the behaviors field, save as many different behaviors as needed, and then execute them when necessary. This is the architecture:



There is not a single rectangle in the diagram for any behavior, since in this architecture the duck's behavior is the same anonymous value, as the number 6 is an anonymous value for the int number variable. The architecture does not care what it does and how the behavior is arranged, so long as it satisfies the Runnable functional interface, just as the int number variable doesn’t matter what number is stored in it, so long as it is integer.

It will be easier to operate the architecture if you prepend a reference book 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("Zzzz")), 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); } } 

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:

  ############################################## one
 I 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 am an exclusive duck
 I spew flame <== exclusive behavior
 ----------------------------------------------
 ############################################## 2
 Kriyaaaaaaaa!  <== replaced in Runtime
 I fly
 Quack quack quack
 ----------------------------------------------
 ############################################## 3
 Zzzz
    ^^^^^ aftermath
 O_o
    ^^^^^ aftermath
 I mallard
    ^^^^^ aftermath
 I fly
    ^^^^^ aftermath
 Quack quack quack
    ^^^^^ aftermath
 ----------------------------------------------
 ############################################## four
 ----------------------------------------------
 ----------------------------------------------
 ----------------------------------------------
 ----------------------------------------------
 I spew flame <== exclusive behavior
 ---------------------------------------------- 


Martin gives Joe a job ...

Joe: What is this? Is this an application architecture? Only two rectangles with one link in the diagram. When I made my version of the program, there were twelve rectangles on my diagram at the very beginning, when ducks could only quack and fly, and they became more than three hundred in six months, when the authorities came up with fifty different aspects of behavior, and for each aspect several different implementations ! I separated the changing from the constant, and created for each group in the architecture its own hierarchy to achieve flexibility and eliminate duplication of code. And you, I see, the changing is generally brought out beyond the limits of architecture, in the area 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 duplication of code very much, except that while creating a mallard and a red-headed duck, the same addition of the ability to quack and fly could be put into a separate method, but these are trifles. I think you will be expanding this application very quickly, so I have more work for you to do. How about creating weather monitors for a weather station? I just received the technical requirements.

Source: https://habr.com/ru/post/350386/


All Articles