Repository: forax/design-pattern-reloaded Branch: master Commit: 2fc54386187c Files: 55 Total size: 106.4 KB Directory structure: gitextract_cnb0lvse/ ├── .github/ │ └── workflows/ │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── src/ │ └── main/ │ └── java/ │ ├── abstractfactory/ │ │ ├── README.md │ │ ├── abstractfactory1.java │ │ └── abstractfactory2.java │ ├── adapter/ │ │ ├── README.md │ │ ├── adapter1.java │ │ └── adapter2.java │ ├── builder/ │ │ ├── README.md │ │ ├── builder1.java │ │ └── builder2.java │ ├── chainofresponsibility/ │ │ ├── README.md │ │ ├── chainofresponsibility1.java │ │ └── chainofresponsibility2.java │ ├── command/ │ │ ├── README.md │ │ ├── command1.java │ │ ├── command2.java │ │ └── command3.java │ ├── decorator/ │ │ ├── README.md │ │ ├── decorator1.java │ │ └── decorator2.java │ ├── factory/ │ │ ├── README.md │ │ ├── factory1.java │ │ └── factory2.java │ ├── memoizer/ │ │ ├── README.md │ │ ├── memoizer1.java │ │ └── memoizer2.java │ ├── monad/ │ │ ├── README.md │ │ ├── monad1.java │ │ ├── monad2.java │ │ └── monad3.java │ ├── observer/ │ │ ├── README.md │ │ ├── observer1.java │ │ ├── observer2.java │ │ └── observer3.java │ ├── railwayswitch/ │ │ ├── README.md │ │ ├── railwayswitch1.java │ │ └── railwayswitch2.java │ ├── state/ │ │ ├── README.md │ │ ├── state1.java │ │ └── state2.java │ ├── templatemethod/ │ │ ├── README.md │ │ ├── templatemethod1.java │ │ └── templatemethod2.java │ ├── typing/ │ │ ├── dynamictyping.java │ │ ├── structuraltyping.java │ │ └── subtyping.java │ └── visitor/ │ ├── README.md │ ├── visitor1.java │ ├── visitor2.java │ └── visitor3.java └── test/ └── test.csv ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/maven.yml ================================================ on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: java: [ 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 - name: setup uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - name: build run: | mvn -B package ================================================ FILE: .gitignore ================================================ *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* /classes/ target/ *.iml .idea/ .classpath .project ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Rémi Forax Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Design Patterns Reloaded Implementation of some design patterns in Java 21, some of them are from the GoF, others are the one you usually see in programs. Like most of the GoF patterns, the implementations use delegation instead of inheritance, but also emphasis immutability when necessary. - [abstract factory](src/main/java/abstractfactory) abstracts access to several factories - [adapter](src/main/java/adapter) to see an object from one interface as one from another interface - [builder](src/main/java/builder) constructs objects by name instead of by position - [chain of responsibility](src/main/java/chainofresponsibility) constructs a pipeline of objects that handle a request - [command](src/main/java/command), objects that are actions - [decorator](src/main/java/decorator) dynamically adds behavior to an existing object - [factory](src/main/java/factory) abstracts the creation of objects - [memoizer](src/main/java/memoizer) caches the result of a computation to avoid multiple re-computations of the same value - [monad](src/main/java/monad) wraps multiple disjoint states under a common API - [observer](src/main/java/observer) de-couples codes by pushing the values from an object to another one - proxy see [decorator](src/main/java/decorator) - singleton see [abstract-factory](src/main/java/abstractfactory) - [railwayswitch](src/main/java/railwayswitch) abstracts a cascade of if ... else - [state](src/main/java/state) delegates API implementation to an internal state object - [template_method](src/main/java/templatemethod) define a behavior with a generic part and a specific part - [typing](src/main/java/typing), 3 kinds of relations between a static type and a runtime class - [visitor / pattern matching](src/main/java/visitor), specify operations on a hierarchy of types outside that hierarchy ## Old materials I've done a presentation in English at Devoxx 2015 Corresponding [slides](https://speakerdeck.com/forax/design-pattern-reloaded-parisjug) used for [my presentation at ParisJUG in June 2015](http://www.parisjug.org/xwiki/wiki/oldversion/view/Meeting/20150602). NB: A port exists in [Scala](https://github.com/YannMoisan/design-pattern-reloaded) ================================================ FILE: pom.xml ================================================ 4.0.0 forax design-pattern-reloaded 1.0-SNAPSHOT UTF-8 21 21 org.junit.jupiter junit-jupiter-api 5.10.0 test org.junit.jupiter junit-jupiter-engine 5.10.0 test ================================================ FILE: src/main/java/abstractfactory/README.md ================================================ # Abstract Factory Let's say we have a simple hierarchy of classes, with a `Bus` and a `Car` both implementing an interface `Vehicle`. ```java interface Vehicle { } record Bus(String color) implements Vehicle { } record Car() implements Vehicle { } ``` ```mermaid classDiagram class Vehicle { <> } class Car { <> } class Bus { <> String color } Vehicle <|.. Car Vehicle <|.. Bus ``` ## Static abstract factory And we want to create a `Vehicle` from a string, "bus" for a `Bus` and "car" for a `Car`, one simple solution is to use a switch on the string ```java sealed interface Vehicle permits Bus, Car { static Vehicle create(String name) { return switch(name) { case "bus" -> new Bus("yellow"); case "car" -> new Car(); default -> throw new IllegalArgumentException("unknown " + name); }; } } record Bus() implements Vehicle { } record Car() implements Vehicle { } ``` The usage is the following ```java Vehicle vehicle = Vehicle.create("bus"); System.out.println(vehicle); // it's a Bus ``` The main issue of this design is that it only works with a closed hierarchy (the interface is declared `sealed`). A user of the interface `Vehicle` can not add a new subtype because all possible classes are handwritten in the `switch`. We can also remark that passing parameters to the creation is not easy, here, we can only create a yellow bus. An abstract factory abstracts over the concept of [factory](../factory) and allows creating instances of open hierarchy classes from parameters. ## Dynamic abstract factory A dynamic abstract factory is an abstract factory (also called a **Registry**) that lets you register [factory](../factory) for a value (here a String) and then calls the factory for that value. It uses a hashtable/dictionary (a `HashMap`) to associate the value to a factory (here a `Supplier`). ```java public class Registry { private final HashMap> map = new HashMap<>(); public void register(String name, Supplier supplier) { map.put(name, supplier); } public Vehicle create(String name) { return map.computeIfAbsent(name, n -> { throw new IllegalArgumentException("Unknown " + n); }) .get(); } } ``` ```mermaid classDiagram class Registry { register(String name, () -> Vehicle supplier) create(String name) Vehicle } class Vehicle { <> } Registry ..> Vehicle : creates ``` The registry is first setup using the method `register` to add the association, then the method `create()` can be called with a value. ```java var registry = new Registry(); registry.register("car", Car::new); registry.register("bus", () -> new Bus("yellow")); var vehicle = registry.create("bus"); System.out.println(vehicle1); // it's a Bus ``` Because the registry contains dynamic associations, a user that creates a new kind of `Vehicle` can register it into the registry too. This design is also flexible in terms of object creation, it's quite easy to register an instance (here `yellowBus`) to always be returned playing the same role as a [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) without the drawbacks of a singleton: you can test it, it's not a global property, its access is tied to the access or the registry. ```java var registry = new Registry(); // as a singleton var yellowBus = new Bus("yellow"); registry.register("bus", () -> yellowBus); ``` This design is the basis of the [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) and its most popular incarnation the [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). The registry can also be separated into two parts because all methods should be called first before calling the method `create` using the [builder pattern](../builder), you can find an example of such transformation in the [command pattern](../command) repository. ================================================ FILE: src/main/java/abstractfactory/abstractfactory1.java ================================================ package abstractfactory; public interface abstractfactory1 { sealed interface Vehicle { static Vehicle create(String name) { return switch(name) { case "bus" -> new Bus("yellow"); case "car" -> new Car(); default -> throw new IllegalArgumentException("unknown " + name); }; } } record Bus(String color) implements Vehicle { } record Car() implements Vehicle { } static void main(String[] args) { var vehicle1 = Vehicle.create("bus"); System.out.println(vehicle1); var vehicle2 = Vehicle.create("car"); System.out.println(vehicle2); } } ================================================ FILE: src/main/java/abstractfactory/abstractfactory2.java ================================================ package abstractfactory; import java.util.HashMap; import java.util.function.Supplier; public interface abstractfactory2 { interface Vehicle { } record Bus(String color) implements Vehicle { } record Car() implements Vehicle { } class Registry { private final HashMap> map = new HashMap<>(); public void register(String name, Supplier supplier) { map.put(name, supplier); } public Vehicle create(String name) { return map.computeIfAbsent(name, n -> { throw new IllegalArgumentException("Unknown " + n); }) .get(); } } static void main(String[] args) { var registry = new Registry(); registry.register("car", Car::new); // as a singleton var yellowBus = new Bus("yellow"); registry.register("bus", () -> yellowBus); var vehicle1 = registry.create("bus"); System.out.println(vehicle1); var vehicle2 = registry.create("car"); System.out.println(vehicle2); } } ================================================ FILE: src/main/java/adapter/README.md ================================================ # Adapter Pattern Let's say we have a simple interface like a `Logger` ```java interface Logger { void log(String message); } ``` and we have an existing API that takes a `Logger` ```java public static void sayHello(Logger logger) { logger.log("hello"); } ``` Now, we introduce another logging API named `Logger2`, perhaps from another library ```java enum Level { WARNING, ERROR } interface Logger2 { void log(Level level, String message); } ``` and we want to use that new interface `Logger2` with the method `sayHello()`. Obviously, we can not directly call `sayHello` with a `Logger2`, we need to do adaptation work. ## Adapter when we do not control Logger2 A `Logger2` is a Logger with a supplementary `Level` so adapting a `Logger2` to be a `Logger` necessitate a level. ```java public static Logger adapt(Logger2 logger2, Level level) { return msg -> logger2.log(level, msg); } ``` And we can use the method `adapt()` like this ```java Logger logger = adapt(logger2, Level.WARNING); logger.log("abort abort !"); ``` An adapter acts a vue that transfers the data from one method of the new interface to a method call to the old interface. ## Adapter when we control Logger2 If we control `Logger2`, the method `adapt()` can be an instance method of `Logger2` ```java interface Logger2 { void log(Level level, String message); default Logger adapt(Level level) { return msg -> log(level, msg); } } ``` In terms of UML diagram, the lambda inside adapt acts as an implementation of `Logger` that delegates its implementation to `Logger2` ```mermaid classDiagram class Logger { <> log(String message); } class LoggerLambda { <> log(String message); } class Logger2 { <> log(Level level, String message) } LoggerLambda <|.. Logger LoggerLambda --> Logger2 : delegates ``` and we can call `adapt()` directly on an instance of `Logger2` ```java Logger logger = logger2.adapt(Level.WARNING); logger.log("abort abort !"); ``` ================================================ FILE: src/main/java/adapter/adapter1.java ================================================ package adapter; public interface adapter1 { interface Logger { void log(String message); } static void sayHello(Logger logger) { logger.log("hello"); } static void main(String[] args) { Logger logger = System.out::println; } } ================================================ FILE: src/main/java/adapter/adapter2.java ================================================ package adapter; public interface adapter2 { interface Logger { void log(String message); } enum Level { WARNING, ERROR } interface Logger2 { void log(Level level, String message); default Logger adapt(Level level) { return msg -> log(level, msg); } } static Logger adapt(Logger2 logger2, Level level) { return msg -> logger2.log(level, msg); } static void main(String[] args) { Logger2 logger2 = (level, msg) -> System.out.println(level + " " + msg); logger2.log(Level.ERROR, "abort abort !"); //Logger logger = adapt(logger2, Level.WARNING); Logger logger = logger2.adapt(Level.WARNING); logger.log("abort abort !"); } } ================================================ FILE: src/main/java/builder/README.md ================================================ # Builder pattern By default, when creating an instance, the arguments of the constructor are passed in order, the association between an argument and a parameter depends on the position of the argument. For example, if we define a `Spaceship` ```java record Spaceship(String name, String captain, int torpedoes, int length) {} ``` an instance is created by calling the constructor ```java var spaceship = new Spaceship("foo", 'baz', 3, 4); ``` The problem is that it's hard to know when reading the last line, which component/property of the record is initialized with which value, apart from taking a look at the definition of the record which can be in another file. A builder improves the readability of the code by introducing method calls (that have a name) to initialize each component. ```java var spaceship = new SpaceshipBuilder() .name("USS Enterprise") .captain("Kirk") .torpedoes(10_000) .length(288_646) .build() ``` A builder is a mutable class that allows to initialize an object by name. All the intermediary methods return the builder itself (`this`) so the method calls can be chained. ```mermaid classDiagram class Spaceship { <> String name String captain int torpedoes int length } class SpaceshipBuilder { name(String name) SpaceshipBuilder captain(String captain) SpaceshipBuilder torpedoes(int torpedoes) SpaceshipBuilder length(int length) SpaceshipBuilder Spaceship build() } SpaceshipBuilder ..> Spaceship : creates ``` ```java public class SpaceshipBuilder { private String name; private String captain; private int torpedoes = -1; private int length = -1; public SpaceshipBuilder name(String name) { this.name = name; return this; } public SpaceshipBuilder captain(String captain) { this.captain = captain; return this; } public SpaceshipBuilder torpedoes(int torpedoes) { this.torpedoes = torpedoes; return this; } public SpaceshipBuilder length(int length) { this.length = length; return this; } public Spaceship build() { if (name == null || captain == null || torpedoes == -1 || length == -1) { throw new IllegalStateException("name, captain, torpedoes or length not initialized"); } return new Spaceship(name, captain, torpedoes, length); } } ``` The main issue with this pattern is that it requires usually quite a lot of code and that IDEs do not track the fact that if a component of the record is renamed, the method of the builder should be renamed too. ## A generic builder Using reflection, it is possible to implement a generic builder that avoids those pitfalls at the price of making the code slower ```java var spaceship = new Builder<>(MethodHandles.lookup(), Spaceship.class) .with(Spaceship::name, "USS Enterprise") .with(Spaceship::captain, "Kirk") .with(Spaceship::torpedoes, 10_000) .with(Spaceship::length, 288_646) .build() ``` A simple implementation is available here [builder2.java](builder2.java). ================================================ FILE: src/main/java/builder/builder1.java ================================================ package builder; public interface builder1 { record Spaceship(String name, String captain, int torpedoes, int length) {} class SpaceshipBuilder { private String name; private String captain; private int torpedoes = -1; private int length = -1; public SpaceshipBuilder name(String name) { this.name = name; return this; } public SpaceshipBuilder captain(String captain) { this.captain = captain; return this; } public SpaceshipBuilder torpedoes(int torpedoes) { this.torpedoes = torpedoes; return this; } public SpaceshipBuilder length(int length) { this.length = length; return this; } public Spaceship build() { if (name == null || captain == null || torpedoes == -1 || length == -1) { throw new IllegalStateException("name, captain, torpedoes or length not initialized"); } return new Spaceship(name, captain, torpedoes, length); } } static void printSpaceship(Spaceship spaceship) { System.out.println(spaceship); } static void main(String[] args) { printSpaceship( new SpaceshipBuilder() .name("USS Enterprise") .captain("Kirk") .torpedoes(10_000) .length(288_646) .build()); } } ================================================ FILE: src/main/java/builder/builder2.java ================================================ package builder; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.invoke.SerializedLambda; import java.lang.reflect.RecordComponent; import java.util.Arrays; import java.util.Map; import static java.lang.invoke.MethodType.methodType; import static java.util.stream.Collectors.toMap; import static java.util.stream.IntStream.range; public interface builder2 { class Builder { public interface Accessor extends Serializable { V apply(T t); } @SuppressWarnings("unchecked") // very wrong but works private static AssertionError rethrow(Throwable cause) throws T { throw (T) cause; } private record HiddenClass(MethodType constructorType, Map slotMap) { } private static final ClassValue HIDDEN_CLASS_VALUE = new ClassValue<>() { @Override protected HiddenClass computeValue(Class type) { var components = type.getRecordComponents(); var slotMap = range(0, components.length) .boxed() .collect(toMap(i -> components[i].getName(), i -> i)); var constructorType= methodType(void.class, Arrays.stream(components).map(RecordComponent::getType).toArray(Class[]::new)); return new HiddenClass(constructorType, slotMap); } }; private final Lookup lookup; private final Class type; private final HiddenClass hiddenClass; private final Object[] values; public Builder(Lookup lookup, Class type) { this.lookup = lookup; this.type = type; var hiddenClass = HIDDEN_CLASS_VALUE.get(type); this.hiddenClass = hiddenClass; values = new Object[hiddenClass.slotMap.size()]; } public Builder with(Accessor accessor, K value) { SerializedLambda lambda; try { var mh = lookup.findVirtual(accessor.getClass(), "writeReplace", methodType(Object.class)); lambda = (SerializedLambda) (Object) mh.invoke(accessor); } catch(NoSuchMethodException | IllegalAccessException e) { throw (LinkageError) new LinkageError().initCause(e); } catch (Throwable e) { throw rethrow(e); } var slot = hiddenClass.slotMap.get(lambda.getImplMethodName()); if (slot == null) { throw new IllegalArgumentException("invalid method reference " + accessor); } values[slot] = value; return this; } public T build() { try { var constructor = lookup.findConstructor(type, hiddenClass.constructorType); return type.cast(constructor.invokeWithArguments(values)); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) { throw (LinkageError) new LinkageError().initCause(e); } catch(Throwable e) { throw rethrow(e); } } } // --- record Spaceship(String name, String captain, int torpedoes, int length) {} static void printSpaceship(Spaceship spaceship) { System.out.println(spaceship); } static void main(String[] args) { printSpaceship( new Builder<>(MethodHandles.lookup(), Spaceship.class) .with(Spaceship::name, "USS Enterprise") .with(Spaceship::captain, "Kirk") .with(Spaceship::torpedoes, 10_000) .with(Spaceship::length, 288_646) .build()); } } ================================================ FILE: src/main/java/chainofresponsibility/README.md ================================================ # Chain of Responsibility Pattern A chain of responsibility is a linked list of action that each take care of a part of a message/request. Let say we have a `Logger` with 3 `Level` of error message. ```java enum Level { INFO, WARNING, ERROR } interface Logger { void log(Level messageLevel, String message); } ``` Now, supose that we want to write a `Logger` that log the message on the console. The idea of the chain of responsibility is not only do the work specific of the `Logger` (logging to the console) but also to delegate the work to the next `Logger` in the chain if it exists. ```java record ConsoleLogger(Level level, Logger logger) implements Logger { @Override public void log(Level messageLevel, String message) { if (messageLevel.compareTo(level) >= 0) { System.out.println("log on console: " + message); } if (logger != null) { logger.log(messageLevel, message); } } } ``` Using the same pattern, we can implement a `Logger` that log the error message on a file ```java record FileLogger(Level level, Logger logger) implements Logger { @Override public void log(Level messageLevel, String message) { if (messageLevel.compareTo(level) >= 0) { System.out.println("log on file: " + message); } if (logger != null) { logger.log(messageLevel, message); } } } ``` ```mermaid classDiagram class Logger { <> log(Level messageLevel, String message) } class ConsoleLogger { <> Level level void log(Level messageLevel, String message) } class FileLogger { <> Level level void log(Level messageLevel, String message) } ConsoleLogger <|.. Logger FileLogger <|.. Logger ConsoleLogger --> Logger : logger FileLogger --> Logger : logger ``` And use the different loggers by creating a linked list of loggers ```java static void main(String[] args) { var logger = new FileLogger(Level.WARNING, new ConsoleLogger(Level.INFO, null)); logger.log(Level.ERROR, "database connection error"); logger.log(Level.INFO, "listen on port 7777"); } ``` ## use delegation to share behavior We can de-structure a bit the pattern by recognizing the checking the message level again the logger level and delegating to another logger operation can be written only once. In that case, the console logger and the file logger are just lambdas ```java interface Logger { void log(Level messageLevel, String message); static Logger console() { return (messageLevel, message) -> System.out.println("log on console: " + message); } static Logger file() { return (messageLevel, message) -> System.out.println("log on file: " + message); } ... ``` and add a [decorator](../decorator) that filter the message depending on the message level ```java ... default Logger withLevel(Level level) { return (messageLevel, message) -> { if (messageLevel.compareTo(level) >= 0) { log(messageLevel, message); } }; } ``` and add another [decorator](../decorator) that delegate the logging of a message to another logger ```java default Logger withLogger(Logger logger) { return (messageLevel, message) -> { log(messageLevel, message); logger.log(messageLevel, message); }; } } ``` Now creating the different logger is done by composing the different loggers ```java static void main(String[] args) { var logger = Logger.file().withLevel(Level.WARNING) .withLogger(Logger.console().withLevel(Level.INFO)); logger.log(Level.ERROR, "database connection error"); logger.log(Level.INFO, "listen on port 7777"); } ``` ================================================ FILE: src/main/java/chainofresponsibility/chainofresponsibility1.java ================================================ package chainofresponsibility; public interface chainofresponsibility1 { enum Level { INFO, WARNING, ERROR } interface Logger { void log(Level messageLevel, String message); } record ConsoleLogger(Level level, Logger logger) implements Logger { @Override public void log(Level messageLevel, String message) { if (messageLevel.compareTo(level) >= 0) { System.out.println("log on console: " + message); } if (logger != null) { logger.log(messageLevel, message); } } } record FileLogger(Level level, Logger logger) implements Logger { @Override public void log(Level messageLevel, String message) { if (messageLevel.compareTo(level) >= 0) { System.out.println("log on file: " + message); } if (logger != null) { logger.log(messageLevel, message); } } } static void main(String[] args) { var logger = new FileLogger(Level.WARNING, new ConsoleLogger(Level.INFO, null)); logger.log(Level.ERROR, "database connection error"); logger.log(Level.INFO, "listen on port 7777"); } } ================================================ FILE: src/main/java/chainofresponsibility/chainofresponsibility2.java ================================================ package chainofresponsibility; public interface chainofresponsibility2 { enum Level { INFO, WARNING, ERROR } interface Logger { void log(Level messageLevel, String message); default Logger withLevel(Level level) { return (messageLevel, message) -> { if (messageLevel.compareTo(level) >= 0) { log(messageLevel, message); } }; } default Logger withLogger(Logger logger) { return (messageLevel, message) -> { log(messageLevel, message); logger.log(messageLevel, message); }; } static Logger console() { return (messageLevel, message) -> System.out.println("log on console: " + message); } static Logger file() { return (messageLevel, message) -> System.out.println("log on file: " + message); } } static void main(String[] args) { var logger = Logger.file().withLevel(Level.WARNING) .withLogger(Logger.console().withLevel(Level.INFO)); logger.log(Level.ERROR, "database connection error"); logger.log(Level.INFO, "listen on port 7777"); } } ================================================ FILE: src/main/java/command/README.md ================================================ # Command Pattern A command pattern is the idea to consider an action as an object, so it can be as simple as using a Runnable or a Consumer. Let's say we have a configuration with several options that can be activated or not depending on the options on the command line, for example, if the option "--long" is present on the command line, the field "longForm" of the configuration should be true. ```java class Config { boolean showHidden = false; boolean longForm = false; boolean showInode = false; boolean showHelp = false; @Override public String toString() { return "Config[showHidden: %s, longForm: %s, showInode: %s, showHelp: %s]" .formatted(showHidden, longForm, showInode, showHelp); } } ``` If we suppose that there is a method `config` that takes a list of arguments and returns a configuration object, the `main`should be something like this ```java var config = config(List.of(args)); System.out.println(config); if (config.showHelp) { System.out.println(""" --all, -a: show hidden files --long, -l: long form --inode, -i: show inodes --help, -h: show this help """); } ``` A straw-man implementation of the method config can be this one ```java static Config config(List args) { var config = new Config(); for(var arg: args) { switch (arg) { case "-a", "--all" -> { if (config.showHidden) { throw new IllegalStateException("--all specified twice"); } config.showHidden = true; } case "-l", "--long" -> { if (config.longForm) { throw new IllegalStateException("--long specified twice"); } config.longForm = true; } case "-i", "--inode" -> { if (config.showInode) { throw new IllegalStateException("--inode specified twice"); } config.showInode = true; } case "-h", "--help" -> { if (config.showHelp) { throw new IllegalStateException("--help specified twice"); } config.showHelp = true; } default -> {} // ignore } } return config; } ``` This implementation is not clean, there is a lot of redundancy in the way having twice the same options is handled and if we want to add a new option we also to not forget to update the help description of the `main`. ## Enter the command pattern The idea is to see an action, here changing on field of the configuration from false to true as an object. Here, our record `Command` also store a `name` to be used to detect if several options correspond to the same command on the command line. ```mermaid classDiagram class CommandRegistry { registerOptions(List~String~ options, String description, Config -> void action) command(String option) Command help() String } class Command { <> String name Config -> void action } CommandRegistry --> "0..*" Command ``` The `CommandRegitry` store the association between an option as a String and the corresponding `Command` and also the help description. ```java record Command(String name, Consumer action) {} class CommandRegistry { private final HashMap map = new HashMap<>(); private final StringBuilder help = new StringBuilder(); public void registerOptions(List options, String description, Consumer action) { var command = new Command(options.get(0), action); options.forEach(option -> map.put(option, command)); help.append(String.join(", ", options)).append(": ").append(description).append("\n"); } public Command command(String option) { return map.get(option); } public String help() { return help.toString(); } } ``` We need a method to configure the `CommandRegistry` i.e. register all the commands with their options ```java static CommandRegistry commandRegistry() { var registry = new CommandRegistry(); registry.registerOptions(List.of("--all", "-a"), "show hidden files", c -> c.showHidden = true); registry.registerOptions(List.of("--long", "-l"), "long form", c -> c.longForm = true); registry.registerOptions(List.of("--inode", "-i"), "show inodes", c -> c.showInode = true); registry.registerOptions(List.of("--help", "-h"), "show this help", c -> c.showHelp = true); return registry; } ``` And we modify the `main` a little, to ask for the `CommmandRegistry` and pass it as parameter of the method `config`. ```java var registry = commandRegistry(); var config = config(registry, List.of(args)); System.out.println(config); if (config.showHelp) { System.out.println(registry.help()); } ``` In the method `config`, we loop over the argument, find the corresponding command (if it's an option) and call the `action` of the `Command` on the `Config` object. We also check that we don't see a command with the same name twice. ```java static Config config(CommandRegistry registry, List args) { var config = new Config(); var commandSet = new HashSet(); for(var arg: args) { var command = registry.command(arg); if (command == null) { continue; // ignore } if (!commandSet.add(command.name)) { throw new IllegalStateException(command.name + " specified twice"); } command.action.accept(config); } return config; } ``` We can change the code of the CommandRegistry a bit because we can recognize that the method `registerOptions` and the method `command` are not called at the same time, so separating them using the [builder pattern](../builder) may make the code easier to use. ## Using a builder The idea is to have a builder to register all the options and to ask for the `CommandRegistry` once all the options with their corresponding command are registered. Let's refactor the code (change the code without changing the API) of `CommandRegistry` ```java static CommandRegistry commandRegistry() { return new CommandRegistry.Builder() .registerOptions(List.of("--all", "-a"), "show hidden files", c -> c.showHidden = true) .registerOptions(List.of("--long", "-l"), "long form", c -> c.longForm = true) .registerOptions(List.of("--inode", "-i"), "show inodes", c -> c.showInode = true) .registerOptions(List.of("--help", "-h"), "show this help", c -> c.showHelp = true) .toRegistry(); } ``` Given that the `CommandRegitry` has no method `registerOptions` anymore, it's just something that encapsulates the `Map` of `Command` and the help description. So it can be modelled by a record like this: ```java record CommandRegistry(Map commandMap, String help) { public static class Builder { private final HashMap map = new HashMap<>(); private final StringBuilder help = new StringBuilder(); public Builder registerOptions(List options, String description, Consumer action) { var command = new Command(options.get(0), action); options.forEach(option -> map.put(option, command)); help.append(String.join(", ", options)).append(": ").append(description).append("\n"); return this; } public CommandRegistry toRegistry() { return new CommandRegistry(Map.copyOf(map), help.toString()); } } public Command command(String option) { return commandMap.get(option); } } ``` ```mermaid classDiagram class CommandRegistryBuilder { registerOptions(List~String~ options, String description, Config -> void action) toRegistry() CommandRegistry } class CommandRegistry { <> String help command(String option) Command } class Command { <> String name Config -> void action } CommandRegistryBuilder ..> CommandRegistry : creates CommandRegistry --> "1..*" Command ``` ================================================ FILE: src/main/java/command/command1.java ================================================ package command; import java.util.List; public interface command1 { class Config { boolean showHidden = false; boolean longForm = false; boolean showInode = false; boolean showHelp = false; @Override public String toString() { return "Config[showHidden: %s, longForm: %s, showInode: %s, showHelp: %s]" .formatted(showHidden, longForm, showInode, showHelp); } } static Config config(List args) { var config = new Config(); for(var arg: args) { switch (arg) { case "-a", "--all" -> { if (config.showHidden) { throw new IllegalStateException("--all specified twice"); } config.showHidden = true; } case "-l", "--long" -> { if (config.longForm) { throw new IllegalStateException("--long specified twice"); } config.longForm = true; } case "-i", "--inode" -> { if (config.showInode) { throw new IllegalStateException("--inode specified twice"); } config.showInode = true; } case "-h", "--help" -> { if (config.showHelp) { throw new IllegalStateException("--help specified twice"); } config.showHelp = true; } default -> {} // ignore } } return config; } static void main(String[] args){ args = new String[] { "--all", "foo", "-i", "--help" }; // DEBUG var config = config(List.of(args)); System.out.println(config); if (config.showHelp) { System.out.println(""" --all, -a: show hidden files --long, -l: long form --inode, -i: show inodes --help, -h: show this help """); } } } ================================================ FILE: src/main/java/command/command2.java ================================================ package command; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.function.Consumer; public interface command2 { class Config { boolean showHidden = false; boolean longForm = false; boolean showInode = false; boolean showHelp = false; @Override public String toString() { return "Config[showHidden: %s, longForm: %s, showInode: %s, showHelp: %s]" .formatted(showHidden, longForm, showInode, showHelp); } } record Command(String name, Consumer action) {} class CommandRegistry { private final HashMap map = new HashMap<>(); private final StringBuilder help = new StringBuilder(); public void registerOptions(List options, String description, Consumer action) { var command = new Command(options.get(0), action); options.forEach(option -> map.put(option, command)); help.append(String.join(", ", options)).append(": ").append(description).append("\n"); } public Command command(String option) { return map.get(option); } public String help() { return help.toString(); } } static Config config(CommandRegistry registry, List args) { var config = new Config(); var commandSet = new HashSet(); for(var arg: args) { var command = registry.command(arg); if (command == null) { continue; // ignore } if (!commandSet.add(command.name)) { throw new IllegalStateException(command.name + " specified twice"); } command.action.accept(config); } return config; } static CommandRegistry commandRegistry() { var registry = new CommandRegistry(); registry.registerOptions(List.of("--all", "-a"), "show hidden files", c -> c.showHidden = true); registry.registerOptions(List.of("--long", "-l"), "long form", c -> c.longForm = true); registry.registerOptions(List.of("--inode", "-i"), "show inodes", c -> c.showInode = true); registry.registerOptions(List.of("--help", "-h"), "show this help", c -> c.showHelp = true); return registry; } static void main(String[] args) { args = new String[] { "--all", "foo", "-i", "--help" }; // DEBUG var registry = commandRegistry(); var config = config(registry, List.of(args)); System.out.println(config); if (config.showHelp) { System.out.println(registry.help()); } } } ================================================ FILE: src/main/java/command/command3.java ================================================ package command; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Consumer; public interface command3 { class Config { boolean showHidden = false; boolean longForm = false; boolean showInode = false; boolean showHelp = false; @Override public String toString() { return "Config[showHidden: %s, longForm: %s, showInode: %s, showHelp: %s]" .formatted(showHidden, longForm, showInode, showHelp); } } record Command(String name, Consumer action) {} record CommandRegistry(Map commandMap, String help) { public static class Builder { private final HashMap map = new HashMap<>(); private final StringBuilder help = new StringBuilder(); public Builder registerOptions(List options, String description, Consumer action) { var command = new Command(options.get(0), action); options.forEach(option -> map.put(option, command)); help.append(String.join(", ", options)).append(": ").append(description).append("\n"); return this; } public CommandRegistry toRegistry() { return new CommandRegistry(Map.copyOf(map), help.toString()); } } public Command command(String option) { return commandMap.get(option); } } static Config config(CommandRegistry registry, List args) { var config = new Config(); var commandSet = new HashSet(); for(var arg: args) { var command = registry.command(arg); if (command == null) { continue; // ignore } if (!commandSet.add(command.name)) { throw new IllegalStateException(command.name + " specified twice"); } command.action.accept(config); } return config; } static CommandRegistry commandRegistry() { return new CommandRegistry.Builder() .registerOptions(List.of("--all", "-a"), "show hidden files", c -> c.showHidden = true) .registerOptions(List.of("--long", "-l"), "long form", c -> c.longForm = true) .registerOptions(List.of("--inode", "-i"), "show inodes", c -> c.showInode = true) .registerOptions(List.of("--help", "-h"), "show this help", c -> c.showHelp = true) .toRegistry(); } static void main(String[] args) { args = new String[] { "--all", "foo", "-i", "--help" }; // DEBUG var registry = commandRegistry(); var config = config(registry, List.of(args)); System.out.println(config); if (config.showHelp) { System.out.println(registry.help()); } } } ================================================ FILE: src/main/java/decorator/README.md ================================================ # The Decorator Pattern A decorator is a simple way to a dynamically enhance and existing behavior using composition. Let say we have an interface `Coffee` that describe a Coffee by its cost and its ingredients ```java interface Coffee { long cost(); // cost of the coffee, in cents String ingredients(); } ``` We can write a simple implementation using a record. ```mermaid classDiagram class Coffee { <> long cost() String ingredients() } class SimpleCoffee { <> long cost String ingredients() } Coffee <|.. SimpleCoffee ``` ```java record SimpleCoffee(long cost) implements Coffee { @Override public String ingredients() { return "Coffee"; } } ``` Let say we now want to represent a coffee with milk, this is a `Coffee` so it should implement the interface `Coffee`, it's cost is the cost of a simple coffee plus 50 cents and the ingredients is coffee and milk. Because the behavior of a coffee with milk depends on the behavior of a coffee, a coffee with milk is created using a coffee and delegates to that instance to get the cost of a coffee and the ingredient of a coffee. ```java record WithMilk(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 50; } @Override public String ingredients() { return coffee.ingredients() + ", Milk"; } } ``` ```mermaid classDiagram class Coffee { <> long cost() String ingredients() } class SimpleCoffee { <> long cost String ingredients() } class WithMilk { <> long cost() String ingredients() } Coffee <|.. SimpleCoffee Coffee <|.. WithMilk WithMilk --> "1" Coffee : coffee ``` So `WithMilk` is simultaneously, a Coffee because it implements the interface `Coffee`and decorates an existing coffee by taking an instance of a Coffee as record component. If we repeat the pattern with `WithSprinkles`. ```java record WithSprinkles(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 20; } @Override public String ingredients() { return coffee.ingredients() + ", Sprinkles"; } } ``` We can see why this pattern is interesting because we have not only created a coffee with sprinkles but also a coffee with milk and with sprinkles. ```java Coffee coffee = new SimpleCoffee(100); Coffee coffeeWithMilk = new WithMilk(coffee); Coffee coffeeWithMilkAndSprinkles = new WithSprinkles(coffeeWithMilk); System.out.println("ingredients: " + coffeeWithMilkAndSprinkles.ingredients()); System.out.println("cost: " + coffeeWithMilkAndSprinkles.cost() + " cents"); ``` This pattern is more powerful than the inheritance because it relies on the delegation which is a dynamic relation while the inheritance/extends is a static relation. ## Improving the API if the hierarchy is sealed We can improve the API if we do not allow users to define their own coffee, we can sealed the interface and provide instance methods (`withMilk`` and `withSprinkles` here) to create a decorator from an existing instance more easily. ```java sealed interface Coffee permits SimpleCoffee, WithMilk, WithSprinkles { long cost(); String ingredients(); static Coffee simple(long cost) { return new SimpleCoffee(cost); } default Coffee withMilk() { return new WithMilk(this); } default Coffee withSprinkles() { return new WithSprinkles(this); } } ``` Here is the same example as above, written using the new API ```java Coffee coffeeWithMilkAndSprinkles = Coffee.simple(100) .withMilk() .withSprinkles(); System.out.println("ingredients: " + coffeeWithMilkAndSprinkles.ingredients()); System.out.println("cost: " + coffeeWithMilkAndSprinkles.cost() + " cents"); ``` Apart from being easier in terms of method discovery, this API also does not explicitly reference the names of the implementations so `SimpleCoffee`, `WithMilk` and `WithSprinkles` can be hidden and non-public. ## Relation with the Proxy Pattern A proxy is a decorator, so it implements an interface and delegate to an instance of that interface but it has a different intent, the idea to intercept method calls to do cross-cutting operations like logging, authentication, caching, etc. Here is an example of a proxy, a Coffee that logs its cost. ```java record LoggingCoffee(Coffee coffee) { @Override public long cost() { var cost = coffee.cost(); System.out.println("coffee cost " + cost); // simulate logging return cost; } @Override public String ingredients() { return coffee.ingredients(); } } ``` ================================================ FILE: src/main/java/decorator/decorator1.java ================================================ package decorator; public interface decorator1 { interface Coffee { long cost(); String ingredients(); } record SimpleCoffee(long cost) implements Coffee { @Override public String ingredients() { return "Coffee"; } } record WithMilk(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 50; } @Override public String ingredients() { return coffee.ingredients() + ", Milk"; } } record WithSprinkles(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 20; } @Override public String ingredients() { return coffee.ingredients() + ", Sprinkles"; } } static void main(String[] args){ Coffee coffee = new SimpleCoffee(100); Coffee coffeeWithMilk = new WithMilk(coffee); Coffee coffeeWithMilkAndSprinkles = new WithSprinkles(coffeeWithMilk); System.out.println("ingredients: " + coffeeWithMilkAndSprinkles.ingredients()); System.out.println("cost: " + coffeeWithMilkAndSprinkles.cost() + " cents"); } } ================================================ FILE: src/main/java/decorator/decorator2.java ================================================ package decorator; public interface decorator2 { sealed interface Coffee { long cost(); String ingredients(); static Coffee simple(long cost) { return new SimpleCoffee(cost); } default Coffee withMilk() { return new WithMilk(this); } default Coffee withSprinkles() { return new WithSprinkles(this); } } record SimpleCoffee(long cost) implements Coffee { @Override public String ingredients() { return "Coffee"; } } record WithMilk(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 50; } @Override public String ingredients() { return coffee.ingredients() + ", Milk"; } } record WithSprinkles(Coffee coffee) implements Coffee { @Override public long cost() { return coffee.cost() + 20; } @Override public String ingredients() { return coffee.ingredients() + ", Sprinkles"; } } static void main(String[] args){ Coffee coffeeWithMilkAndSprinkles = Coffee.simple(100) .withMilk() .withSprinkles(); System.out.println("ingredients: " + coffeeWithMilkAndSprinkles.ingredients()); System.out.println("cost: " + coffeeWithMilkAndSprinkles.cost() + " cents"); } } ================================================ FILE: src/main/java/factory/README.md ================================================ # Factory A factory abstracts the creation of instances. Conceptually, it's a function that returns a different instances each time the function is called. Let's take an example, we have, at least, two kinds of `Vehicle` a `Car` and a `Bus` both takes a `Color` as parameter. ```mermaid classDiagram class Vehicle { <> } class Car { <> } class Bus { <> } class Color { <> RED BLUE } Vehicle <|.. Car Vehicle <|.. Bus Car --> "1" Color : color Bus --> "1" Color : color ``` ```java enum Color { RED, BLUE } interface Vehicle { } record Car(Color color) implements Vehicle { } record Bus(Color color) implements Vehicle { } ``` A factory let you use the same code to create instances of the same kind+color. For example, if we want to create 5 vehicles, with a method `create5()` like this. ```java static List create5(Supplier factory) { return Stream.generate(factory).limit(5).toList(); } ``` A Supplier is a predefined function interfac of Java that takes nothing and return an object. We can then create 5 red cars that way. ```java Supplier redCarFactory = () -> new Car(Color.RED); System.out.println(create5(redCarFactory)); ``` ## A factory with parameters A factory can also have parameters, in our example a color and we can specify that color to create a vehicle with the right color. ```java @FunctionalInterface interface VehicleFactory { Vehicle create(Color color); default Supplier bind(Color color) { return () -> create(color); } } ``` ```mermaid classDiagram class Vehicle { <> } class VehicleFactory { <> create(Color color) Vehicle } VehicleFactory ..> Vehicle : creates ``` Because `VehicleFactory` is a functional interface, it can be initialized that way to create cars. ```java VehicleFactory carFactory = Car::new; ``` We can also [adapt](../adapter) the `VehicleFactory` to be seen as a supplier by providing the color. ```java var supplier = carFactory.bind(Color.RED); System.out.println(create5(supplier)); ``` ================================================ FILE: src/main/java/factory/factory1.java ================================================ package factory; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; public interface factory1 { enum Color { RED, BLUE } interface Vehicle { } record Car(Color color) implements Vehicle { } record Bus(Color color) implements Vehicle { } static List create5(Supplier factory) { return Stream.generate(factory).limit(5).toList(); } static void main(String[] args) { Supplier redCarFactory = () -> new Car(Color.RED); Supplier blueBusFactory = () -> new Bus(Color.BLUE); System.out.println(create5(redCarFactory)); System.out.println(create5(blueBusFactory)); } } ================================================ FILE: src/main/java/factory/factory2.java ================================================ package factory; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; public interface factory2 { enum Color { RED, BLUE } interface Vehicle { } record Car(Color color) implements Vehicle { } record Bus(Color color) implements Vehicle { } @FunctionalInterface interface VehicleFactory { Vehicle create(Color color); default Supplier bind(Color color) { return () -> create(color); } } static List create5(Supplier factory) { return Stream.generate(factory).limit(5).toList(); } static void main(String[] args) { VehicleFactory carFactory = Car::new; VehicleFactory busFactory = Bus::new; System.out.println(create5(carFactory.bind(Color.RED))); System.out.println(create5(busFactory.bind(Color.BLUE))); } } ================================================ FILE: src/main/java/memoizer/README.md ================================================ # Memoizer Pattern The result of pure function with one parameter, i.e. a function that has its return value only depending on the value of the parameter with no side effect, can be stored in a cache to avoid re-computation of this value. The Memoizer Pattern provided a reusable class `Memoizer` allowing add a cache to the computation of a pure function. ## The memoizer pattern using inheritance We can use the [Template Method](../templatemethod) pattern and inheritance to create a class that let users overriding the method `compute()` while providing a method `memoize` that cache the result of a computation. ```java abstract class Memoizer { private final HashMap map = new HashMap<>(); public final R memoize(V value) { return map.computeIfAbsent(value, this::compute); } protected abstract R compute(V value); } ``` ```mermaid classDiagram class Memoizer~V,R~ { <> memoize(V value) R #compute(V value) R } ``` This is how it can be used: ```java var memoizer = new Memoizer() { @Override protected Integer compute(Integer n) { if (n < 2) { return 1; } return memoize(n - 1) + memoize(n - 2); } }; ``` But as usual with inheritance, it's easy to mis-use that class because the API contains both the méthod `compute()` and `memoize()` leading to two frequent mistake, either the computation in `compute` calling recursively the method `compute()` instead of `memoize()` or a user using the method `compute()` directly instead of the method `memoize()`. This second mistake is less frequent because `compute` is declared protected an not public. ## The Memoizer Pattern using delegation As usual the solution is to avoid inheritance and use delegation instead. So the class `Memoizer` can be written that way ```java final class Memoizer { private final Function function; private final HashMap map = new HashMap<>(); public Memoizer(Function function) { this.function = function; } public R memoize(V value) { return map.computeIfAbsent(value, function); } } ``` but in that case, the method `memoize()` is not available inside the lambda ```java var memoizer = new Memoizer(n -> { if (n < 2) { return 1; } return memoize(n - 1) + memoize(n - 2); //FIXME !! }); ``` To solve that, we need to introduce a second parameter able to do recursive call, like this ```java var fibo = new Memoizer((n, fib) -> { if (n < 2) { return 1; } return fib.apply(n - 1) + fib.apply(n - 2); }); ``` this leads to the following code for the class `Memoizer` ```java final class Memoizer { private final BiFunction, ? extends R> bifunction; private final HashMap map = new HashMap<>(); public Memoizer(BiFunction, ? extends R> bifunction) { this.bifunction = bifunction; } public R memoize(V value) { return map.computeIfAbsent(value, v -> bifunction.apply(v, this::memoize)); } } ``` ```mermaid classDiagram class Memoizer~V,R~ { Memoizer((V, (V, R)) -> R biFunction) memoize(V value) R } ``` ================================================ FILE: src/main/java/memoizer/memoizer1.java ================================================ package memoizer; import static java.util.stream.IntStream.range; import java.util.HashMap; public interface memoizer1 { abstract class Memoizer { private final HashMap map = new HashMap<>(); public final R memoize(V value) { return map.computeIfAbsent(value, this::compute); } protected abstract R compute(V value); } static void main(String[] args) { var memoizer = new Memoizer() { @Override protected Integer compute(Integer n) { if (n < 2) { return 1; } return memoize(n - 1) + memoize(n - 2); } }; range(0, 20).map(memoizer::memoize).forEach(System.out::println); } } ================================================ FILE: src/main/java/memoizer/memoizer2.java ================================================ package memoizer; import static java.util.stream.IntStream.range; import java.util.HashMap; import java.util.function.BiFunction; import java.util.function.Function; public interface memoizer2 { final class Memoizer { private final BiFunction, ? extends R> bifunction; private final HashMap map = new HashMap<>(); public Memoizer(BiFunction, ? extends R> bifunction) { this.bifunction = bifunction; } public R memoize(V value) { return map.computeIfAbsent(value, v -> bifunction.apply(v, this::memoize)); } } static void main(String[] args) { var fibo = new Memoizer((n, fib) -> { if (n < 2) { return 1; } return fib.apply(n - 1) + fib.apply(n - 2); }); range(0, 20).map(fibo::memoize).forEach(System.out::println); } } ================================================ FILE: src/main/java/monad/README.md ================================================ # Monad Pattern A monad is a class that encapsulates several disjoint states. Let say we have a `User` that comes from an untrusted source, by example, deserialized from a JSON request. ```java record User(String name, int age) { } ``` We want to validate that none of the values of that object is illegal, a simple solution is to write a method `validateUser` like this ```java public static void validateUser(User user) { if (user.name().isEmpty()) { throw new IllegalArgumentException("name is empty"); } if (!(user.age() > 0 && user.age() < 150)) { throw new IllegalArgumentException("age is not between 0 and 150"); } } ``` The problem with this solution is that if several values are illegal, only the first one is reported. A solution is to delay the report of the errors, gathering all of them and at the end report of all them. This requires to have an object that can store the value to validate (here a user) and all the error at the same time. ## Enter the monad ```java record Error(IllegalArgumentException e, Error next) { } record Validator(V value, Error error) { public Validator check(Predicate validation, String message) { if (!validation.test(value)) { return new Validator<>(value, new Error(new IllegalArgumentException(message), error)); } return this; } public V orElseThrow() throws IllegalStateException { if (error == null) { return value; } var exception = new IllegalStateException(); Stream.iterate(error, Objects::nonNull, Error::next).map(Error::e).forEach(exception::addSuppressed); throw exception; } } ``` A monad stores both the value and an error (here a linked list of exceptions). It has two kind of methods - intermediary operation, here `check(predicate, message)` that checks the value and return a new instance of monad with the states updated - final operations that stop the processing, here `orElseThrow()` that returns the value or reports the exceptions. ```mermaid classDiagram class Validator~V~ { <> V value Error error check(Predicate~V~ validation, String message) Validator~V~ orElseThrow() V } ``` With that `validateUser()` can be written that way ```java public static User validateUser(User user) { return new Validator<>(user, null) .check(u -> !u.name().isEmpty(), "name is empty") .check(u -> u.age() >= 0 && u.age() <= 150, "age is not between 0 and 150") .orElseThrow(); } ``` ## Improve the API by separating the accessor from the operation done on the accessor value We can add an overloads to `check` that takes 2 parameters, a reference to an accessor method (like `User::name`) and a predicate to cleanly express what test is done on what value. ```java private static IntPredicate inBetween(int start, int end) { return value -> value >= start && value <= end; } public static User validateUser(User user) { return new Validator<>(user, null) .check(User::name, not(String::isEmpty), "name is empty") .check(User::age, inBetween(0, 150)::test, "age is not between 0 and 150") .orElseThrow(); } ``` In that case, this method `check()` can be written using the function composition and delegate to the method `check()` with one parameter. ```java record Validator(V value, Error error) { ... public Validator check(Function projection, Predicate validation, String message) { return check(projection.andThen(validation::test)::apply, message); } ... } ``` The JDK contains two monads [java.util.Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) and [java.util.stream.Stream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html). ================================================ FILE: src/main/java/monad/monad1.java ================================================ package monad; public interface monad1 { record User(String name, int age) { } static void validateUser(User user) { if (user.name().isEmpty()) { throw new IllegalArgumentException("name is empty"); } if (!(user.age() > 0 && user.age() < 150)) { throw new IllegalArgumentException("age is not between 0 and 150"); } } static void main(String[] args) { // no monad var user = new User("bob", 12); //var user = new User("", -12); validateUser(user); } } ================================================ FILE: src/main/java/monad/monad2.java ================================================ package monad; import monad.monad1.User; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; public interface monad2 { record User(String name, int age) { } record Error(IllegalArgumentException e, Error next) { } record Validator(V value, Error error) { public V orElseThrow() throws IllegalStateException { if (error == null) { return value; } var exception = new IllegalStateException(); Stream.iterate(error, Objects::nonNull, Error::next).map(Error::e).forEach(exception::addSuppressed); throw exception; } public Validator check(Predicate validation, String message) { if (!validation.test(value)) { return new Validator<>(value, new Error(new IllegalArgumentException(message), error)); } return this; } } static User validateUser(User user) { return new Validator<>(user, null) .check(u -> !u.name().isEmpty(), "name is empty") .check(u -> u.age() >= 0 && u.age() <= 150, "age is not between 0 and 150") .orElseThrow(); } static void main(String[] args) { var user = new User("bob", 12); //var user = new User("", -12); validateUser(user); } } ================================================ FILE: src/main/java/monad/monad3.java ================================================ package monad; import java.util.Objects; import java.util.function.Function; import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.stream.Stream; import static java.util.function.Predicate.not; public interface monad3 { record User(String name, int age) { } record Error(IllegalArgumentException e, Error next) { } record Validator(V value, Error error) { public V orElseThrow() throws IllegalStateException { if (error == null) { return value; } var exception = new IllegalStateException(); Stream.iterate(error, Objects::nonNull, Error::next).map(Error::e).forEach(exception::addSuppressed); throw exception; } public Validator check(Predicate validation, String message) { if (!validation.test(value)) { return new Validator<>(value, new Error(new IllegalArgumentException(message), error)); } return this; } public Validator check(Function projection, Predicate validation, String message) { return check(projection.andThen(validation::test)::apply, message); } } static IntPredicate inBetween(int start, int end) { return value -> value >= start && value <= end; } static User validateUser(User user) { return new Validator<>(user, null) .check(User::name, not(String::isEmpty), "name is empty") .check(User::age, inBetween(0, 150)::test, "age is not between 0 and 150") .orElseThrow(); } static void main(String[] args) { var user = new User("bob", 12); //var user = new User("", -12); validateUser(user); } } ================================================ FILE: src/main/java/observer/README.md ================================================ # Observer Pattern The point of the observer pattern is to decouple two pieces of code by using an interface in the middle. Let say we want to model a `StockExchange` that have a balance and contains several quantities of stocks. For example, here the balance is 5 000 and wa have 1 000 stocks of FOOGL and 2 000 stocks of PAPL. ```java static void main(String[] args) { var stockExchange = new StockExchange(); stockExchange.setBalance(5_000); stockExchange.setStockQuantity("FOOGL", 1_000); stockExchange.setStockQuantity("PAPL", 2_000); System.out.println(stockExchange); ``` ```mermaid classDiagram class Order { <> Kind kind int quantity String tick int accountId } class StockExchange { setBalance(int balance) setStockQuantity(String tick, int quantity) process(List~Order~ orders) List~Order~ } StockExchange ..> Order : process ``` An exchange is able to process orders and group the rejected orders by `accountId`. An order is rejected if the exchange has no enough stock to process a BUY order, here the last BUY order can not be processed because the exchange has not 3 000 stocks of FOOGL. ```java record Order(Kind kind, int quantity, String tick, int accountId) { enum Kind { BUY, SELL } } static void main(String[] args) { ... var orders = List.of( new Order(Order.Kind.SELL, 200, "FOOGL", 12), new Order(Order.Kind.BUY, 1_500, "PAPL", 12), new Order(Order.Kind.BUY, 3_000, "FOOGL", 666) ); var rejectedOrderList = stockExchange.process(orders); var rejectedOrders = rejectedOrderList.stream() .collect(Collectors.groupingBy(Order::accountId)); ``` This is the code of the `StockExchange` ```java class StockExchange { private final TreeMap stockMap = new TreeMap<>(); private int balance; @Override public String toString() { return "{stockMap: " + stockMap + ", balance: " + balance + "}"; } public void setBalance(int balance) { this.balance = balance; } public void setStockQuantity(String tick, int quantity) { stockMap.put(tick, quantity); } public List process(List orders) { var rejectedOrders = new ArrayList(); for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { rejectedOrders.add(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); } } } return rejectedOrders; } } ``` We now want to add a code to log if the balance is less than 0 or more than 6 000, because having a negative balance is always bad and having too much money in a hot wallet is bad too. We can patch the code of `process` to in case of a BUY check if the balance does not grow over 6 000 or in case of a SELL if the balance does not go below 0, but it will make the code hard to maintain because we will be mixed the processing algorithm with other concerns. It's better to decouple those thing by introducing an observer. ## Enter the observer An observer is an interface used to publish the state of an object so a code can react to it. In our example, let's define a `BalanceObserver` that will be called each time the balance change ```java interface BalanceObserver { void balanceChanged(int newValue); } ``` ```mermaid classDiagram class BalanceObserver { <> balanceChanged(int newValue) } class StockExchange { StockExchange(BalanceObserver balanceObserver) process(List~Order~ orders) List~Order~ } StockExchange --> "1" BalanceObserver : balanceObserver ``` We take the `BalanceObserver` at creation and called it each time the balance changed ```java class StockExchange { private final BalanceObserver balanceObserver; private final TreeMap stockMap = new TreeMap<>(); private int balance; public StockExchange(BalanceObserver balanceObserver) { this.balanceObserver = balanceObserver; } ... public List process(List orders) { var rejectedOrders = new ArrayList(); for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { rejectedOrders.add(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); balanceObserver.balanceChanged(balance); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); balanceObserver.balanceChanged(balance); } } } return rejectedOrders; } } ``` We can now implement the observer with the correct semantics ```java BalanceObserver balanceObserver = newValue -> { if (newValue < 0) { System.out.println("balance negative !!!"); return; } if (newValue >= 6_000) { System.out.println("balance too high !!!"); } }; var stockExchange = new StockExchange(balanceObserver); ... ``` As you can see, the code that reacts to the value of the balance being changed is separated from the code that process the orders thanks to the observer pattern. Note: historically, the observer pattern was called the observable/observer pattern and was able to have manage several observers instead of one like in the example above. We now prefer to have only one observer and to use the design __pattern composite__ in case we have several observers. Here is an example of such composite ```java record CompositeObserver(List observers) implements Observer { public void balanceChanged(int newValue) { observers.forEach(observer -> observer.balanceChanged(value)) } } ``` ## More on observers If we take a look to the initial code, there was already an observer hidden between the lines, instead of using a list to collect the rejected orders, we should also apply the same principle and declare an observer of the rejected orders. ```mermaid classDiagram class OrderObserver { <> rejected(Order order) } class StockExchange { process(List~Order~ orders, OrderObserver orderObserver) List~Order~ } StockExchange ..> OrderObserver : uses ``` ```java interface OrderObserver { void rejected(Order order); } ... public void process(List orders, OrderObserver orderObserver) { for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { orderObserver.rejected(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); balanceObserver.balanceChanged(balance); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); balanceObserver.balanceChanged(balance); } } } } ... ``` You can note that this observer does not have to be stored in a field if it is only useful for a treatment. And in the `main()`, we can create the list that will store all rejected orders ```java ... var rejectedOrderList = new ArrayList(); stockExchange.process(orders, rejectedOrderList::add); var rejectedOrders = rejectedOrderList.stream() .collect(Collectors.groupingBy(Order::accountId)); ... ``` or using a stream + mapMulti(), avoid the creation of the intermediary list ```java ... var rejectedOrders = Stream.of(stockExchange) .mapMulti((exchange, consumer) -> exchange.process(orders, consumer::accept)) .collect(Collectors.groupingBy(Order::accountId)); ... ``` The Observer Pattern allow to decouple/untangle a code to create an on the shelf class that can be reused because it depends on an interface. ================================================ FILE: src/main/java/observer/observer1.java ================================================ package observer; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public interface observer1 { record Order(Kind kind, int quantity, String tick, int accountId) { enum Kind { BUY, SELL } } class StockExchange { private final TreeMap stockMap = new TreeMap<>(); private int balance; @Override public String toString() { return "{stockMap: " + stockMap + ", balance: " + balance + "}"; } public void setBalance(int balance) { this.balance = balance; } public void setStockQuantity(String tick, int quantity) { stockMap.put(tick, quantity); } public List process(List orders) { var rejectedOrders = new ArrayList(); for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { rejectedOrders.add(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); } } } return rejectedOrders; } } static void main(String[] args) { var stockExchange = new StockExchange(); stockExchange.setBalance(5_000); stockExchange.setStockQuantity("FOOGL", 1_000); stockExchange.setStockQuantity("PAPL", 2_000); System.out.println(stockExchange); var orders = List.of( new Order(Order.Kind.SELL, 200, "FOOGL", 12), new Order(Order.Kind.BUY, 1_500, "PAPL", 12), new Order(Order.Kind.BUY, 3_000, "FOOGL", 666) ); var rejectedOrderList = stockExchange.process(orders); var rejectedOrders = rejectedOrderList.stream() .collect(Collectors.groupingBy(Order::accountId)); System.out.println(stockExchange); System.out.println("rejected orders " + rejectedOrders); } } ================================================ FILE: src/main/java/observer/observer2.java ================================================ package observer; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public interface observer2 { record Order(Kind kind, int quantity, String tick, int accountId) { enum Kind { BUY, SELL } } interface BalanceObserver { void balanceChanged(int newValue); } class StockExchange { private final BalanceObserver balanceObserver; private final TreeMap stockMap = new TreeMap<>(); private int balance; public StockExchange(BalanceObserver balanceObserver) { this.balanceObserver = balanceObserver; } @Override public String toString() { return "{stockMap: " + stockMap + ", balance: " + balance + "}"; } public void setBalance(int balance) { this.balance = balance; } public void setStockQuantity(String tick, int quantity) { stockMap.put(tick, quantity); } public List process(List orders) { var rejectedOrders = new ArrayList(); for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { rejectedOrders.add(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); balanceObserver.balanceChanged(balance); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); balanceObserver.balanceChanged(balance); } } } return rejectedOrders; } } static void main(String[] args) { BalanceObserver balanceObserver = newValue -> { if (newValue < 0) { System.out.println("balance negative !!!"); return; } if (newValue >= 6_000) { System.out.println("balance too high !!!"); } }; var stockExchange = new StockExchange(balanceObserver); stockExchange.setBalance(5_000); stockExchange.setStockQuantity("FOOGL", 1_000); stockExchange.setStockQuantity("PAPL", 2_000); System.out.println(stockExchange); var orders = List.of( new Order(Order.Kind.SELL, 200, "FOOGL", 12), new Order(Order.Kind.BUY, 1_500, "PAPL", 12), new Order(Order.Kind.BUY, 3_000, "FOOGL", 666) ); var rejectedOrderList = stockExchange.process(orders); var rejectedOrders = rejectedOrderList.stream() .collect(Collectors.groupingBy(Order::accountId)); System.out.println(stockExchange); System.out.println("rejected orders " + rejectedOrders); } } ================================================ FILE: src/main/java/observer/observer3.java ================================================ package observer; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; public interface observer3 { record Order(Kind kind, int quantity, String tick, int accountId) { enum Kind { BUY, SELL } } interface BalanceObserver { void balanceChanged(int newValue); } interface OrderObserver { void rejected(Order order); } class StockExchange { private final BalanceObserver balanceObserver; private final TreeMap stockMap = new TreeMap<>(); private int balance; public StockExchange(BalanceObserver balanceObserver) { this.balanceObserver = balanceObserver; } @Override public String toString() { return "{stockMap: " + stockMap + ", balance: " + balance + "}"; } public void setBalance(int balance) { this.balance = balance; } public void setStockQuantity(String tick, int quantity) { stockMap.put(tick, quantity); } public void process(List orders, OrderObserver orderObserver) { for (var order : orders) { switch (order.kind()) { case BUY -> { var stockQuantity = stockMap.getOrDefault(order.tick(), 0); if (order.quantity() > stockQuantity) { orderObserver.rejected(order); continue; } stockMap.put(order.tick(), stockQuantity - order.quantity()); balance += order.quantity(); balanceObserver.balanceChanged(balance); } case SELL -> { stockMap.merge(order.tick(), order.quantity(), Integer::sum); balance -= order.quantity(); balanceObserver.balanceChanged(balance); } } } } } static void main(String[] args) { BalanceObserver balanceObserver = newValue -> { if (newValue < 0) { System.out.println("balance negative !!!"); return; } if (newValue >= 6_000) { System.out.println("balance too high !!!"); } }; var stockExchange = new StockExchange(balanceObserver); stockExchange.setBalance(5_000); stockExchange.setStockQuantity("FOOGL", 1_000); stockExchange.setStockQuantity("PAPL", 2_000); System.out.println(stockExchange); var orders = List.of( new Order(Order.Kind.SELL, 200, "FOOGL", 12), new Order(Order.Kind.BUY, 1_500, "PAPL", 12), new Order(Order.Kind.BUY, 3_000, "FOOGL", 666) ); var rejectedOrders = Stream.of(stockExchange) .mapMulti((exchange, consumer) -> exchange.process(orders, consumer::accept)) .collect(Collectors.groupingBy(Order::accountId)); System.out.println(stockExchange); System.out.println("rejected orders " + rejectedOrders); } } ================================================ FILE: src/main/java/railwayswitch/README.md ================================================ # Railway Switch The railway switch pattern, quoted by [Scott Wlaschin](https://fsharpforfunandprofit.com/rop/), is a way to abstract a cascade of **if ... else** to have a mode declarative way of specify how a value can be obtained. For example, we may want to determine the value of a hostname by either reading the environment variable "HOSTNAME" or the Java property "hostname" or the property "hostname" of a configuration file or to use "localhost". This can be written that way ```java public static String findHostname() { // 1 var hostName = System.getenv("HOSTNAME"); if (hostName != null) { return hostName; } // 2 hostName = System.getProperty("hostname"); if (hostName != null) { return hostName; } // 3 var properties = new Properties(); try (var reader = Files.newBufferedReader(Path.of(".config"))) { properties.load(reader); hostName = properties.getProperty("hostname"); } catch (IOException e) { hostName = null; } if (hostName != null) { return hostName; } // 4 return "localhost"; } ``` ## Railway switch pattern The aim of the railway switch pattern is to simplify codes that do a cascade of **if ... else** by creating higher level constructs (functions), here `environment()`, `systemProperty()`, `fileProperty()`, and a way to compose them (the function `or()`). For our example, we want a code like this ```java static String findHostname() { return environment("HOSTNAME") .or(systemProperty("hostname")) .or(fileProperty(Path.of(".config"), "hostname")) .find() .orElse("localhost"); } ``` For that, we first create a functional interface (`Finder`) that either return a value or no value (we use an `Optional` here) and an instance method `or` able to combine the result of two finders. ```mermaid classDiagram class Finder { <> find() Optional~String~ or(Finder finder) Finder } ``` ```java @FunctionalInterface public interface Finder { Optional find(); default Finder or(Finder finder) { return () -> find().or(finder::find); } } ``` Using a functional interface here allow to delay the computation in order to not do a side effect like reading the configuration file if the value of the property have been found by one of the function before. Once we have our interface `Finder` we can rewrite each code that try to find the value as a method that returns a `Finder`. ```java static Finder environment(String name) { return () -> Optional.ofNullable(System.getenv(name)); } static Finder systemProperty(String name) { return () -> Optional.ofNullable(System.getProperty(name)); } static Finder fileProperty(Path path, String name) { return () -> { var properties = new Properties(); try (var reader = Files.newBufferedReader(path)) { properties.load(reader); return Optional.ofNullable(properties.getProperty(name)); } catch (IOException e) { return Optional.empty(); } }; } ``` The railway switch pattern helps to move from a code describing how to reach an objective to a more declarative API separating how something can be found from how those things are combined. ================================================ FILE: src/main/java/railwayswitch/railwayswitch1.java ================================================ package railwayswitch; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; public interface railwayswitch1 { static String findHostname() { // 1 var hostName = System.getenv("HOSTNAME"); if (hostName != null) { return hostName; } // 2 hostName = System.getProperty("hostname"); if (hostName != null) { return hostName; } // 3 var properties = new Properties(); try (var reader = Files.newBufferedReader(Path.of(".config"))) { properties.load(reader); hostName = properties.getProperty("hostname"); } catch (IOException e) { hostName = null; } if (hostName != null) { return hostName; } // 4 return "localhost"; } static void main(String[] args) { var hostname = findHostname(); System.out.println(hostname); } } ================================================ FILE: src/main/java/railwayswitch/railwayswitch2.java ================================================ package railwayswitch; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.Properties; public interface railwayswitch2 { @FunctionalInterface interface Finder { Optional find(); default Finder or(Finder finder) { return () -> find().or(finder::find); } } static Finder environment(String name) { return () -> Optional.ofNullable(System.getenv(name)); } static Finder systemProperty(String name) { return () -> Optional.ofNullable(System.getProperty(name)); } static Finder fileProperty(Path path, String name) { return () -> { var properties = new Properties(); try (var reader = Files.newBufferedReader(path)) { properties.load(reader); return Optional.ofNullable(properties.getProperty(name)); } catch (IOException e) { return Optional.empty(); } }; } static String findHostname() { return environment("HOSTNAME") .or(systemProperty("hostname")) .or(fileProperty(Path.of(".config"), "hostname")) .find() .orElse("localhost"); } static void main(String[] args) { var hostname = findHostname(); System.out.println(hostname); } } ================================================ FILE: src/main/java/state/README.md ================================================ # The State Pattern When you have a workflow and you move from one state to another, it's often easier to read the code if each state is its own object. Let say there is a cart, I can add article to it, then I will pay for all the articles in it then I will ship the articles. ```mermaid classDiagram class Article { <> String name long price } class CreditCard { <> String name String id } class Address { <> String address String country } ``` Here is an example of use (`info()` display the values of the state) ```java var cart = new Cart(); cart.add(new Article("Lego Kit", 9999)); System.out.println(cart.info()); cart.buy(new CreditCard("Mr Nobody", "1212_2121_1212_2121")); System.out.println(cart.info()); cart.ship(new Address("12 Nice Street, London", "England")); System.out.println(cart.info()); ``` Represented as an automata, there are 3 states, `CREATED`, `PAYED`, `SHIPPED` and 3 actions (transition) `add`, `buy` and `ship`. ```mermaid flowchart LR CREATED -- buy --> PAYED PAYED -- ship --> SHIPPED CREATED -- add --> CREATED ``` ## An implementation with an enum One naive implementation is to use an enum to represent the different states ```mermaid classDiagram class State { <> CREATED PAYED SHIPPED } class Cart { add(Article article) buy(CreditCard creditCard) ship(Address address) info() String } Cart --> "1" State : state Cart --> "0..*" Article : articles Cart --> "0..1" Address : address ``` ```java class Cart { private enum State { CREATED, PAYED, SHIPPED } private List
articles = new ArrayList<>(); private Address address; private State state = State.CREATED; public void add(Article article) { if (state != State.CREATED) { throw new IllegalStateException(); } articles.add(article); } public void buy(CreditCard creditCard) { if (state != State.CREATED) { throw new IllegalStateException(); } state = State.PAYED; articles = List.copyOf(articles); } public void ship(Address address) { if (state != State.PAYED) { throw new IllegalStateException(); } this.address = address; state = State.SHIPPED; } public String info() { return switch (state) { case CREATED -> "created articles " + articles; case PAYED -> "payed articles " + articles; case SHIPPED -> "shipped articles " + articles + " to " + address; }; } } ``` but it is not clear which values (`articles` and `address`) is available at which state and it makes all values mutable. ## An implementation with a hierarchy The idea of the state pattern is that each state is represented by a different classes and all transitions are represented by pure functions, the mutable part is dealt in `Cart` by re-writing the field `state` so each implementation of the interface `State` can be immutable. ```mermaid classDiagram class Cart { add(Article article) buy(CreditCard creditCard) ship(Address address) info() String } class State { <> add(Article article) buy(CreditCard creditCard) ship(Address address) info() String } class Created { <> } class Payed { <> } class Shipped { <> } Created <|.. State Payed <|.. State Shipped <|.. State Cart --> "1" State : state Created --> "0..*" Article : articles Payed --> "0..*" Article : articles Shipped --> "0..*" Article : articles Shipped --> "0..1" Address : address ``` ```java class Cart { private sealed interface State { State add(Article article); State buy(CreditCard creditCard); State ship(Address address); } private record Created(ArrayList
articles) implements State { ... } private record Payed(List
articles) implements State { ... } private record Shipped(List
articles, Address address) implements State { ... } private State state = new Created(new ArrayList<>()); public void add(Article article) { state = state.add(article); } public void buy(CreditCard creditCard) { state = state.buy(creditCard); } public void ship(Address address) { state = state.ship(address); } public String info() { return state.toString(); } } ``` Instead of having to implement all methods, in all subtypes of `State`, we provide an implementation by default that throw an exception. ```java private sealed interface State { default State add(Article article) { throw new IllegalStateException(); } default State buy(CreditCard creditCard) { throw new IllegalStateException(); } default State ship(Address address) { throw new IllegalStateException(); } } ``` This is similar to adding a non-public abstract class in between the interface `State` and the implementations if the implementations are classes. Here, we use records that do not support inheritance. For the state `Created`, we have a transition `add()` that cycle back to the same state (so it returns `this`) and a transition `buy()` that return a new state `Payed`. For `Payed`, we have a transition `ship()` that returns a new state `Payed` which has no transition. You can also remark that `Created` takes an `ArrayList` of articles because at that point it's a mutable structure (and the record is declared `private` so there is no problem to expose the implementation). `Payed` and `Shipped`, both takes an immutable list. ```java private record Created(ArrayList
articles) implements State { @Override public State add(Article article) { articles.add(article); return this; } @Override public State buy(CreditCard creditCard) { return new Payed(List.copyOf(articles)); } } private record Payed(List
articles) implements State { @Override public State ship(Address address) { return new Shipped(articles, address); } } private record Shipped(List
articles, Address address) implements State { } ``` The **State Pattern** splits the implementation on to each immutable state instead of having everything mutable in `Cart`. ================================================ FILE: src/main/java/state/state1.java ================================================ package state; import java.util.ArrayList; import java.util.List; public interface state1 { record Article(String name, long price) {} record CreditCard(String name, String id) {} record Address(String address, String country) {} class Cart { private enum State { CREATED, PAYED, SHIPPED } private List
articles = new ArrayList<>(); private Address address; private State state = State.CREATED; public void add(Article article) { if (state != State.CREATED) { throw new IllegalStateException(); } articles.add(article); } public void buy(CreditCard creditCard) { if (state != State.CREATED) { throw new IllegalStateException(); } state = State.PAYED; articles = List.copyOf(articles); } public void ship(Address address) { if (state != State.PAYED) { throw new IllegalStateException(); } this.address = address; state = State.SHIPPED; } public String info() { return switch (state) { case CREATED -> "created articles " + articles; case PAYED -> "payed articles " + articles; case SHIPPED -> "shipped articles " + articles + " to " + address; }; } } static void main(String[] args){ var cart = new Cart(); cart.add(new Article("Lego Kit", 9999)); System.out.println(cart.info()); cart.buy(new CreditCard("Mr Nobody", "1212_2121_1212_2121")); System.out.println(cart.info()); cart.ship(new Address("12 Nice Street, London", "England")); System.out.println(cart.info()); } } ================================================ FILE: src/main/java/state/state2.java ================================================ package state; import java.util.ArrayList; import java.util.List; public interface state2 { record Article(String name, long price) {} record CreditCard(String name, String id) {} record Address(String address, String country) {} class Cart { private sealed interface State { default State add(Article article) { throw new IllegalStateException(); } default State buy(CreditCard creditCard) { throw new IllegalStateException(); } default State ship(Address address) { throw new IllegalStateException(); } } private record Created(ArrayList
articles) implements State { @Override public State add(Article article) { articles.add(article); return this; } @Override public State buy(CreditCard creditCard) { return new Payed(List.copyOf(articles)); } } private record Payed(List
articles) implements State { @Override public State ship(Address address) { return new Shipped(articles, address); } } private record Shipped(List
articles, Address address) implements State { } private State state = new Created(new ArrayList<>()); public void add(Article article) { state = state.add(article); } public void buy(CreditCard creditCard) { state = state.buy(creditCard); } public void ship(Address address) { state = state.ship(address); } public String info() { return state.toString(); } } static void main(String[] args){ var cart = new Cart(); cart.add(new Article("Lego Kit", 9999)); System.out.println(cart.info()); cart.buy(new CreditCard("Mr Nobody", "1212_2121_1212_2121")); System.out.println(cart.info()); cart.ship(new Address("12 Nice Street, London", "England")); System.out.println(cart.info()); } } ================================================ FILE: src/main/java/templatemethod/README.md ================================================ # Template Method Pattern The idea of the template method pattern is to decompose a problem in two parts, a generic reusable part and a specific part. ## Using inheritance Let's say our generic part add hearts to a String and the specific part returns the string to be decorated. Using an abstract class `DecoratedString, the abstract method `plainString()` returns the string to be decorated and the method `loveString()` the generic part that adds a heart before and after the string. ```java abstract class DecoratedString { protected abstract String plainString(); public String loveString() { return "❤️ " + plainString() + " ❤️"; } } ``` and the specific part use inheritance (here with an anonymous class) to specify the string to be decorated. ```java static void main(String[] args) { var decoratedString = new DecoratedString() { @Override protected String plainString() { return "hello"; } }; System.out.println(decoratedString.loveString()); } ``` Using inheritance here as usual is not the best solution because the API of `DecoratedString` contains both methods even if it may not make sense. In particular, inside the method `plainString()`, one can call the method `loveString()` leading to a recursive loop that will blow the stack. ## Using delegation A better design is to use delegation instead of inheritance. The class `DecoratedString` takes a `Supplier` at construction and call it in `loveString()`. ```java class DecoratedString { private final Supplier supplier; public DecoratedString(Supplier supplier) { this.supplier = supplier; } public String loveString() { return "❤️ " + supplier.get() + " ❤️"; } } ``` A use site, the supplier can be specified as a lambda ```java static void main(String[] args) { var decoratedString = new DecoratedString(() -> "hello"); System.out.println(decoratedString.loveString()); } ``` The [Memoizer Pattern](../memoizer) is a more advanced example of template method pattern. ================================================ FILE: src/main/java/templatemethod/templatemethod1.java ================================================ package templatemethod; public interface templatemethod1 { abstract class DecoratedString { protected abstract String plainString(); public String loveString() { return "❤️ " + plainString() + " ❤️"; } } static void main(String[] args) { var decoratedString = new DecoratedString() { @Override protected String plainString() { return "hello"; } }; System.out.println(decoratedString.loveString()); } } ================================================ FILE: src/main/java/templatemethod/templatemethod2.java ================================================ package templatemethod; import java.util.function.Supplier; import java.util.function.UnaryOperator; public interface templatemethod2 { class DecoratedString { private final Supplier supplier; public DecoratedString(Supplier supplier) { this.supplier = supplier; } public String loveString() { return "❤️ " + supplier.get() + " ❤️"; } } static void main(String[] args) { var decoratedString = new DecoratedString(() -> "hello"); System.out.println(decoratedString.loveString()); } } ================================================ FILE: src/main/java/typing/dynamictyping.java ================================================ package typing; public interface dynamictyping { class A { public void m() { System.out.println("A::m"); } } class B { public void m() { System.out.println("B::m"); } } static void print(Object o) throws Exception { o.getClass().getMethod("m").invoke(o); } public static void main(String[] args) throws Exception { print(new A()); print(new B()); } } ================================================ FILE: src/main/java/typing/structuraltyping.java ================================================ package typing; public interface structuraltyping { interface I { void m(); } class A { public void m() { System.out.println("A::m"); } } class B { public void m() { System.out.println("B::m"); } } static void print(I i) { i.m(); } static void main(String[] args) { print(new A()::m); print(new B()::m); } } ================================================ FILE: src/main/java/typing/subtyping.java ================================================ package typing; public interface subtyping { interface I { void m(); } class A implements I { public void m() { System.out.println("A::m"); } } class B implements I { public void m() { System.out.println("B::m"); } } static void print(I i) { i.m(); } static void main(String[] args) { print(new A()); print(new B()); } } ================================================ FILE: src/main/java/visitor/README.md ================================================ # The Visitor Pattern The aim of Visitor Pattern is to be able to specify operations on a hierarchy of classes outside that hierarchy. ## Double dispatch Let say we have the following closed/sealed hierarchy ```mermaid classDiagram class Vehicle { <> } class Car { <> } class CarHauler { <> } Vehicle <|.. Car Vehicle <|.. CarHauler CarHauler --> "0..*" Car : cars ``` ```java sealed interface Vehicle permits Car, CarHauler { } record Car() implements Vehicle { } record CarHauler(List cars) implements Vehicle {} ``` and we want to specify operation outside that hierarchy. For that we need an interface and a method per concrete classes. ```java interface Visitor { R visitCar(Car car); R visitCarHauler(CarHauler carHauler); } ``` then if we want by example count the number of cars, we can write the following visitor ```java static int count(Vehicle vehicle) { var visitor = new Visitor() { @Override public Integer visitCar(Car car) { return 1; } @Override public Integer visitCarHauler(CarHauler carHauler) { return 1 + carHauler.cars().stream().mapToInt(car -> car.accept(this)).sum(); } }; return vehicle.accept(visitor); } ``` You can notice that we are using a mysterious method `accept()`. We can not directly call one of the methods visit* of the interface `Visitor` with a `Vehicle` because we don't know witch one to call. The method acts as a trampoline, when called on a vehicle with the visitor as parameter , it calls back the right method of the `Visitor`. So the hierarchy of vehicles needs to be modified to add the method `accept()`. ```mermaid classDiagram class Visitor~R~ { <> visitCar(Car car) R visitCarHauler(CarHauler carHauler) R } class Vehicle { <> R accept(Visitor~R~ visitor) } class Car { <> R accept(Visitor~R~ visitor) } class CarHauler { <> R accept(Visitor~R~ visitor) } Vehicle <|.. Car Vehicle <|.. CarHauler CarHauler --> "0..*" Car : cars Car ..> Visitor: delegates CarHauler ..> Visitor: delegates ``` ```java sealed interface Vehicle permits Car, CarHauler { R accept(Visitor visitor); } record Car() implements Vehicle { @Override public R accept(Visitor visitor) { return visitor.visitCar(this); } } record CarHauler(List cars) implements Vehicle { @Override public R accept(Visitor visitor) { return visitor.visitCarHauler(this); } } ``` This technique to call the method `accept` that will then call the right method `visit*` of the Visitor is called the _double dispatch_ because effectively, there is one dynamic dispatch to call the right method accept followed by another dynamic dispatch call through the Visitor interface. The drawback of this design is that it requires the hierarchy to be modified to add the method `accept` and only works on sealed hierarchy because you can not add new method in the `Visitor` interface. The other issue is that because the visit is done outside the hierarchy, the class have to have accessor to access the component values (the method `CarHauler.cars() in our example). ## Dynamic visitor with an open hierarchy And what if we want the visitor to work on an open hierarchy ? It means that we can not use an interface `Visitor` anymore, but you can replace it by as many functions as methods of the interface. In that case, `Visitor` becomes a class with a method `when` that associate for a class the function to call (as a lambda) and a method `call` that for an instance of a `Vehicle` dynamically find its class and calls the corresponding function. ```mermaid classDiagram class Visitor~R~ { when(Class~T~ type, T -> R fun) Visitor~R~ call(Object receiver) R } ``` ```java static int count(Vehicle vehicle) { var visitor = new Visitor(); visitor.when(Car.class, car -> 1) .when(CarHauler.class, carHauler -> 1 + carHauler.cars().stream().mapToInt(visitor::call).sum()); return visitor.call(vehicle); } ``` To do the association between a class and the corresponding visiting function, we use a hash table ```java public class Visitor { private final HashMap, Function> map = new HashMap<>(); public Visitor when(Class type, Function fun) { map.put(type, fun.compose(type::cast)); return this; } public R call(Object receiver) { var receiverClass = receiver.getClass(); return map.computeIfAbsent(receiverClass, k -> { throw new IllegalArgumentException("invalid " + k.getName()); }) .apply(receiver); } } ``` You can notice that in order to compile, we need to see a function that take a subtype of `Vehicle`as a funtion that takes an `Object`. This is done by done a dynamic cast (the method reference `type::cast`). ## Pattern matching The visitor pattern is a way to a switch on a hierarchy of classes, starting with Java 21 we can now do a switch on a sealed interface. ```java static int count(Vehicle vehicle) { return switch (vehicle) { case Car car -> 1; case CarHauler carHauler -> 1 + carHauler.cars().stream().mapToInt(car -> count(car)).sum(); }; } ``` Like with the double dispatch, this requires the hierarchy to be sealed otherwise the compiler do not know if the cases cover all possible subtypes. Java also allow to match all the record components using a record pattern ```java static int count(Vehicle vehicle) { return switch (vehicle) { case Car() -> 1; case CarHauler(List cars) -> 1 + cars.stream().mapToInt(car -> count(car)).sum(); }; } ``` In the future, this capability will be extended to match not only records but also classes ================================================ FILE: src/main/java/visitor/visitor1.java ================================================ package visitor; import java.util.List; public interface visitor1 { sealed interface Vehicle { R accept(Visitor visitor); } record Car() implements Vehicle { @Override public R accept(Visitor visitor) { return visitor.visitCar(this); } } record CarHauler(List cars) implements Vehicle { @Override public R accept(Visitor visitor) { return visitor.visitCarHauler(this); } } interface Visitor { R visitCar(Car car); R visitCarHauler(CarHauler carHauler); } static int count(Vehicle vehicle) { var visitor = new Visitor() { @Override public Integer visitCar(Car car) { return 1; } @Override public Integer visitCarHauler(CarHauler carHauler) { return 1 + carHauler.cars().stream().mapToInt(car -> car.accept(this)).sum(); } }; return vehicle.accept(visitor); } static void main(String[] args) { var vehicle = new CarHauler(List.of(new Car(), new Car())); var count = count(vehicle); System.out.println(count); } } ================================================ FILE: src/main/java/visitor/visitor2.java ================================================ package visitor; import java.util.HashMap; import java.util.List; import java.util.function.Function; public interface visitor2 { interface Vehicle { } record Car() implements Vehicle { } record CarHauler(List cars) implements Vehicle {} class Visitor { private final HashMap, Function> map = new HashMap<>(); public Visitor when(Class type, Function fun) { map.put(type, fun.compose(type::cast)); return this; } public R call(Object receiver) { var receiverClass = receiver.getClass(); return map.computeIfAbsent(receiverClass, k -> { throw new IllegalArgumentException("invalid " + k.getName()); }) .apply(receiver); } } static int count(Vehicle vehicle) { var visitor = new Visitor(); visitor.when(Car.class, car -> 1) .when(CarHauler.class, carHauler -> 1 + carHauler.cars().stream().mapToInt(visitor::call).sum()); return visitor.call(vehicle); } static void main(String[] args) { var vehicle = new CarHauler(List.of(new Car(), new Car())); var count = count(vehicle); System.out.println(count); } } ================================================ FILE: src/main/java/visitor/visitor3.java ================================================ package visitor; import java.util.List; public interface visitor3 { sealed interface Vehicle { } record Car() implements Vehicle { } record CarHauler(List cars) implements Vehicle {} static int count(Vehicle vehicle) { return switch (vehicle) { case Car() -> 1; case CarHauler(List cars) -> 1 + cars.stream().mapToInt(car -> count(car)).sum(); }; } static void main(String[] args) { var vehicle = new CarHauler(List.of(new Car(), new Car())); var count = count(vehicle); System.out.println(count); } } ================================================ FILE: test/test.csv ================================================ 1, 2, 4, 6.007 4, 5, 6 3