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
================================================
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>forax</groupId>
<artifactId>design-pattern-reloaded</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
================================================
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 {
<<interface>>
}
class Car {
<<record>>
}
class Bus {
<<record>>
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<String, Supplier<? extends Vehicle>> map = new HashMap<>();
public void register(String name, Supplier<? extends Vehicle> 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 {
<<interface>>
}
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<String, Supplier<? extends Vehicle>> map = new HashMap<>();
public void register(String name, Supplier<? extends Vehicle> 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 {
<<interface>>
log(String message);
}
class LoggerLambda {
<<lambda>>
log(String message);
}
class Logger2 {
<<interface>>
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 {
<<record>>
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<T extends Record> {
public interface Accessor<T, V> extends Serializable {
V apply(T t);
}
@SuppressWarnings("unchecked") // very wrong but works
private static <T extends Throwable> AssertionError rethrow(Throwable cause) throws T {
throw (T) cause;
}
private record HiddenClass(MethodType constructorType, Map<String, Integer> slotMap) { }
private static final ClassValue<HiddenClass> 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<T> type;
private final HiddenClass hiddenClass;
private final Object[] values;
public Builder(Lookup lookup, Class<T> type) {
this.lookup = lookup;
this.type = type;
var hiddenClass = HIDDEN_CLASS_VALUE.get(type);
this.hiddenClass = hiddenClass;
values = new Object[hiddenClass.slotMap.size()];
}
public <K> Builder<T> with(Accessor<? super T, ? extends K> 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 {
<<interface>>
log(Level messageLevel, String message)
}
class ConsoleLogger {
<<record>>
Level level
void log(Level messageLevel, String message)
}
class FileLogger {
<<record>>
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<String> 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 {
<<record>>
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<Config> action) {}
class CommandRegistry {
private final HashMap<String, Command> map = new HashMap<>();
private final StringBuilder help = new StringBuilder();
public void registerOptions(List<String> options, String description, Consumer<Config> 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<String> args) {
var config = new Config();
var commandSet = new HashSet<String>();
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<String, Command> commandMap, String help) {
public static class Builder {
private final HashMap<String, Command> map = new HashMap<>();
private final StringBuilder help = new StringBuilder();
public Builder registerOptions(List<String> options, String description, Consumer<Config> 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 {
<<record>>
String help
command(String option) Command
}
class Command {
<<record>>
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<String> 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<Config> action) {}
class CommandRegistry {
private final HashMap<String, Command> map = new HashMap<>();
private final StringBuilder help = new StringBuilder();
public void registerOptions(List<String> options, String description, Consumer<Config> 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<String> args) {
var config = new Config();
var commandSet = new HashSet<String>();
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<Config> action) {}
record CommandRegistry(Map<String, Command> commandMap, String help) {
public static class Builder {
private final HashMap<String, Command> map = new HashMap<>();
private final StringBuilder help = new StringBuilder();
public Builder registerOptions(List<String> options, String description, Consumer<Config> 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<String> args) {
var config = new Config();
var commandSet = new HashSet<String>();
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 {
<<interface>>
long cost()
String ingredients()
}
class SimpleCoffee {
<<record>>
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 {
<<interface>>
long cost()
String ingredients()
}
class SimpleCoffee {
<<record>>
long cost
String ingredients()
}
class WithMilk {
<<record>>
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 {
<<interface>>
}
class Car {
<<record>>
}
class Bus {
<<record>>
}
class Color {
<<enumeration>>
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<Vehicle> create5(Supplier<Vehicle> 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<Vehicle> 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<Vehicle> bind(Color color) {
return () -> create(color);
}
}
```
```mermaid
classDiagram
class Vehicle {
<<interface>>
}
class VehicleFactory {
<<interface>>
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<Vehicle> create5(Supplier<Vehicle> factory) {
return Stream.generate(factory).limit(5).toList();
}
static void main(String[] args) {
Supplier<Vehicle> redCarFactory = () -> new Car(Color.RED);
Supplier<Vehicle> 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<Vehicle> bind(Color color) {
return () -> create(color);
}
}
static List<Vehicle> create5(Supplier<Vehicle> 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<V, R> {
private final HashMap<V, R> 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~ {
<<abstract>>
memoize(V value) R
#compute(V value) R
}
```
This is how it can be used:
```java
var memoizer = new Memoizer<Integer, Integer>() {
@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<V, R> {
private final Function<? super V, ? extends R> function;
private final HashMap<V, R> map = new HashMap<>();
public Memoizer(Function<? super V, ? extends R> 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<Integer, Integer>(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<Integer, Integer>((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<V, R> {
private final BiFunction<? super V, Function<? super V, ? extends R>, ? extends R> bifunction;
private final HashMap<V, R> map = new HashMap<>();
public Memoizer(BiFunction<? super V, Function<? super V, ? extends R>, ? 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<V, R> {
private final HashMap<V, R> 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<Integer, Integer>() {
@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<V, R> {
private final BiFunction<? super V, Function<? super V, ? extends R>, ? extends R> bifunction;
private final HashMap<V, R> map = new HashMap<>();
public Memoizer(BiFunction<? super V, Function<? super V, ? extends R>, ? 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<Integer, Integer>((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>(V value, Error error) {
public Validator<V> check(Predicate<? super V> 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~ {
<<record>>
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>(V value, Error error) {
...
public <U> Validator<V> check(Function<? super V, ? extends U> projection, Predicate<? super U> 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>(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<V> check(Predicate<? super V> 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>(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<V> check(Predicate<? super V> validation, String message) {
if (!validation.test(value)) {
return new Validator<>(value, new Error(new IllegalArgumentException(message), error));
}
return this;
}
public <U> Validator<V> check(Function<? super V, ? extends U> projection, Predicate<? super U> 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 {
<<record>>
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<String, Integer> 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<Order> process(List<? extends Order> orders) {
var rejectedOrders = new ArrayList<Order>();
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 {
<<interface>>
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<String, Integer> stockMap = new TreeMap<>();
private int balance;
public StockExchange(BalanceObserver balanceObserver) {
this.balanceObserver = balanceObserver;
}
...
public List<Order> process(List<? extends Order> orders) {
var rejectedOrders = new ArrayList<Order>();
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<Observer> 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 {
<<interface>>
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<? extends Order> 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<Order>();
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)
.<Order>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<String, Integer> 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<Order> process(List<? extends Order> orders) {
var rejectedOrders = new ArrayList<Order>();
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<String, Integer> 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<Order> process(List<? extends Order> orders) {
var rejectedOrders = new ArrayList<Order>();
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<String, Integer> 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<? extends Order> 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)
.<Order>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 {
<<interface>>
find() Optional~String~
or(Finder finder) Finder
}
```
```java
@FunctionalInterface
public interface Finder {
Optional<String> 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<String> 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 {
<<record>>
String name
long price
}
class CreditCard {
<<record>>
String name
String id
}
class Address {
<<record>>
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 {
<<enumeration>>
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<Article> 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 {
<<interface>>
add(Article article)
buy(CreditCard creditCard)
ship(Address address)
info() String
}
class Created {
<<record>>
}
class Payed {
<<record>>
}
class Shipped {
<<record>>
}
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<Article> articles) implements State { ... }
private record Payed(List<Article> articles) implements State { ... }
private record Shipped(List<Article> 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<Article> 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<Article> articles) implements State {
@Override
public State ship(Address address) {
return new Shipped(articles, address);
}
}
private record Shipped(List<Article> 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<Article> 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<Article> 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<Article> articles) implements State {
@Override
public State ship(Address address) {
return new Shipped(articles, address);
}
}
private record Shipped(List<Article> 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<String> supplier;
public DecoratedString(Supplier<String> 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<String> supplier;
public DecoratedString(Supplier<String> 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 {
<<interface>>
}
class Car {
<<record>>
}
class CarHauler {
<<record>>
}
Vehicle <|.. Car
Vehicle <|.. CarHauler
CarHauler --> "0..*" Car : cars
```
```java
sealed interface Vehicle permits Car, CarHauler { }
record Car() implements Vehicle { }
record CarHauler(List<Car> 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> {
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<Integer>() {
@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~ {
<<interface>>
visitCar(Car car) R
visitCarHauler(CarHauler carHauler) R
}
class Vehicle {
<<interface>>
R accept(Visitor~R~ visitor)
}
class Car {
<<record>>
R accept(Visitor~R~ visitor)
}
class CarHauler {
<<record>>
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> R accept(Visitor<? extends R> visitor);
}
record Car() implements Vehicle {
@Override
public <R> R accept(Visitor<? extends R> visitor) {
return visitor.visitCar(this);
}
}
record CarHauler(List<Car> cars) implements Vehicle {
@Override
public <R> R accept(Visitor<? extends R> 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<Integer>();
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<R> {
private final HashMap<Class<?>, Function<Object, ? extends R>> map = new HashMap<>();
public <T> Visitor<R> when(Class<? extends T> type, Function<? super T, ? extends R> 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<Car> 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> R accept(Visitor<? extends R> visitor);
}
record Car() implements Vehicle {
@Override
public <R> R accept(Visitor<? extends R> visitor) {
return visitor.visitCar(this);
}
}
record CarHauler(List<Car> cars) implements Vehicle {
@Override
public <R> R accept(Visitor<? extends R> visitor) {
return visitor.visitCarHauler(this);
}
}
interface Visitor<R> {
R visitCar(Car car);
R visitCarHauler(CarHauler carHauler);
}
static int count(Vehicle vehicle) {
var visitor = new Visitor<Integer>() {
@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<Car> cars) implements Vehicle {}
class Visitor<R> {
private final HashMap<Class<?>, Function<Object, ? extends R>> map = new HashMap<>();
public <T> Visitor<R> when(Class<? extends T> type, Function<? super T, ? extends R> 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<Integer>();
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<Car> cars) implements Vehicle {}
static int count(Vehicle vehicle) {
return switch (vehicle) {
case Car() -> 1;
case CarHauler(List<Car> 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
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
SYMBOL INDEX (268 symbols across 35 files)
FILE: src/main/java/abstractfactory/abstractfactory1.java
type abstractfactory1 (line 3) | public interface abstractfactory1 {
type Vehicle (line 4) | sealed interface Vehicle {
method create (line 5) | static Vehicle create(String name) {
method main (line 16) | static void main(String[] args) {
FILE: src/main/java/abstractfactory/abstractfactory2.java
type abstractfactory2 (line 6) | public interface abstractfactory2 {
type Vehicle (line 7) | interface Vehicle { }
class Registry (line 11) | class Registry {
method register (line 14) | public void register(String name, Supplier<? extends Vehicle> suppli...
method create (line 18) | public Vehicle create(String name) {
method main (line 24) | static void main(String[] args) {
FILE: src/main/java/adapter/adapter1.java
type adapter1 (line 3) | public interface adapter1 {
type Logger (line 4) | interface Logger {
method log (line 5) | void log(String message);
method sayHello (line 8) | static void sayHello(Logger logger) {
method main (line 12) | static void main(String[] args) {
FILE: src/main/java/adapter/adapter2.java
type adapter2 (line 3) | public interface adapter2 {
type Logger (line 4) | interface Logger {
method log (line 5) | void log(String message);
type Level (line 8) | enum Level { WARNING, ERROR }
type Logger2 (line 9) | interface Logger2 {
method log (line 10) | void log(Level level, String message);
method adapt (line 12) | default Logger adapt(Level level) {
method adapt (line 17) | static Logger adapt(Logger2 logger2, Level level) {
method main (line 21) | static void main(String[] args) {
FILE: src/main/java/builder/builder1.java
type builder1 (line 3) | public interface builder1 {
class SpaceshipBuilder (line 6) | class SpaceshipBuilder {
method name (line 12) | public SpaceshipBuilder name(String name) {
method captain (line 16) | public SpaceshipBuilder captain(String captain) {
method torpedoes (line 20) | public SpaceshipBuilder torpedoes(int torpedoes) {
method length (line 24) | public SpaceshipBuilder length(int length) {
method build (line 29) | public Spaceship build() {
method printSpaceship (line 37) | static void printSpaceship(Spaceship spaceship) {
method main (line 41) | static void main(String[] args) {
FILE: src/main/java/builder/builder2.java
type builder2 (line 16) | public interface builder2 {
class Builder (line 17) | class Builder<T extends Record> {
type Accessor (line 18) | public interface Accessor<T, V> extends Serializable {
method apply (line 19) | V apply(T t);
method rethrow (line 22) | @SuppressWarnings("unchecked") // very wrong but works
method computeValue (line 31) | @Override
method Builder (line 48) | public Builder(Lookup lookup, Class<T> type) {
method with (line 56) | public <K> Builder<T> with(Accessor<? super T, ? extends K> accessor...
method build (line 74) | public T build() {
method printSpaceship (line 90) | static void printSpaceship(Spaceship spaceship) {
method main (line 94) | static void main(String[] args) {
FILE: src/main/java/chainofresponsibility/chainofresponsibility1.java
type chainofresponsibility1 (line 3) | public interface chainofresponsibility1 {
type Level (line 4) | enum Level {
type Logger (line 7) | interface Logger {
method log (line 8) | void log(Level messageLevel, String message);
method log (line 12) | @Override
method log (line 24) | @Override
method main (line 35) | static void main(String[] args) {
FILE: src/main/java/chainofresponsibility/chainofresponsibility2.java
type chainofresponsibility2 (line 3) | public interface chainofresponsibility2 {
type Level (line 4) | enum Level {
type Logger (line 7) | interface Logger {
method log (line 8) | void log(Level messageLevel, String message);
method withLevel (line 10) | default Logger withLevel(Level level) {
method withLogger (line 18) | default Logger withLogger(Logger logger) {
method console (line 25) | static Logger console() {
method file (line 29) | static Logger file() {
method main (line 34) | static void main(String[] args) {
FILE: src/main/java/command/command1.java
type command1 (line 5) | public interface command1 {
class Config (line 6) | class Config {
method toString (line 12) | @Override
method config (line 19) | static Config config(List<String> args) {
method main (line 53) | static void main(String[] args){
FILE: src/main/java/command/command2.java
type command2 (line 8) | public interface command2 {
class Config (line 9) | class Config {
method toString (line 15) | @Override
class CommandRegistry (line 24) | class CommandRegistry {
method registerOptions (line 28) | public void registerOptions(List<String> options, String description...
method command (line 34) | public Command command(String option) {
method help (line 38) | public String help() {
method config (line 43) | static Config config(CommandRegistry registry, List<String> args) {
method commandRegistry (line 59) | static CommandRegistry commandRegistry() {
method main (line 68) | static void main(String[] args) {
FILE: src/main/java/command/command3.java
type command3 (line 9) | public interface command3 {
class Config (line 10) | class Config {
method toString (line 16) | @Override
class Builder (line 26) | public static class Builder {
method registerOptions (line 30) | public Builder registerOptions(List<String> options, String descript...
method toRegistry (line 37) | public CommandRegistry toRegistry() {
method command (line 42) | public Command command(String option) {
method config (line 47) | static Config config(CommandRegistry registry, List<String> args) {
method commandRegistry (line 63) | static CommandRegistry commandRegistry() {
method main (line 72) | static void main(String[] args) {
FILE: src/main/java/decorator/decorator1.java
type decorator1 (line 3) | public interface decorator1 {
type Coffee (line 4) | interface Coffee {
method cost (line 5) | long cost();
method ingredients (line 6) | String ingredients();
method ingredients (line 10) | @Override
method cost (line 17) | @Override
method ingredients (line 21) | @Override
method cost (line 28) | @Override
method ingredients (line 32) | @Override
method main (line 38) | static void main(String[] args){
FILE: src/main/java/decorator/decorator2.java
type decorator2 (line 3) | public interface decorator2 {
type Coffee (line 4) | sealed interface Coffee {
method cost (line 5) | long cost();
method ingredients (line 6) | String ingredients();
method simple (line 8) | static Coffee simple(long cost) {
method withMilk (line 11) | default Coffee withMilk() {
method withSprinkles (line 14) | default Coffee withSprinkles() {
method ingredients (line 20) | @Override
method cost (line 27) | @Override
method ingredients (line 31) | @Override
method cost (line 38) | @Override
method ingredients (line 42) | @Override
method main (line 48) | static void main(String[] args){
FILE: src/main/java/factory/factory1.java
type factory1 (line 7) | public interface factory1 {
type Color (line 8) | enum Color { RED, BLUE }
type Vehicle (line 9) | interface Vehicle { }
method create5 (line 13) | static List<Vehicle> create5(Supplier<Vehicle> factory) {
method main (line 17) | static void main(String[] args) {
FILE: src/main/java/factory/factory2.java
type factory2 (line 7) | public interface factory2 {
type Color (line 8) | enum Color { RED, BLUE }
type Vehicle (line 9) | interface Vehicle { }
type VehicleFactory (line 13) | @FunctionalInterface
method create (line 15) | Vehicle create(Color color);
method bind (line 17) | default Supplier<Vehicle> bind(Color color) {
method create5 (line 22) | static List<Vehicle> create5(Supplier<Vehicle> factory) {
method main (line 26) | static void main(String[] args) {
FILE: src/main/java/memoizer/memoizer1.java
type memoizer1 (line 7) | public interface memoizer1 {
class Memoizer (line 8) | abstract class Memoizer<V, R> {
method memoize (line 11) | public final R memoize(V value) {
method compute (line 15) | protected abstract R compute(V value);
method main (line 18) | static void main(String[] args) {
FILE: src/main/java/memoizer/memoizer2.java
type memoizer2 (line 9) | public interface memoizer2 {
class Memoizer (line 10) | final class Memoizer<V, R> {
method Memoizer (line 14) | public Memoizer(BiFunction<? super V, Function<? super V, ? extends ...
method memoize (line 18) | public R memoize(V value) {
method main (line 23) | static void main(String[] args) {
FILE: src/main/java/monad/monad1.java
type monad1 (line 3) | public interface monad1 {
method validateUser (line 6) | static void validateUser(User user) {
method main (line 15) | static void main(String[] args) { // no monad
FILE: src/main/java/monad/monad2.java
type monad2 (line 9) | public interface monad2 {
method orElseThrow (line 15) | public V orElseThrow() throws IllegalStateException {
method check (line 24) | public Validator<V> check(Predicate<? super V> validation, String mess...
method validateUser (line 32) | static User validateUser(User user) {
method main (line 39) | static void main(String[] args) {
FILE: src/main/java/monad/monad3.java
type monad3 (line 11) | public interface monad3 {
method orElseThrow (line 17) | public V orElseThrow() throws IllegalStateException {
method check (line 26) | public Validator<V> check(Predicate<? super V> validation, String mess...
method check (line 33) | public <U> Validator<V> check(Function<? super V, ? extends U> project...
method inBetween (line 38) | static IntPredicate inBetween(int start, int end) {
method validateUser (line 42) | static User validateUser(User user) {
method main (line 49) | static void main(String[] args) {
FILE: src/main/java/observer/observer1.java
type observer1 (line 8) | public interface observer1 {
type Kind (line 10) | enum Kind { BUY, SELL }
class StockExchange (line 13) | class StockExchange {
method toString (line 17) | @Override
method setBalance (line 22) | public void setBalance(int balance) {
method setStockQuantity (line 26) | public void setStockQuantity(String tick, int quantity) {
method process (line 30) | public List<Order> process(List<? extends Order> orders) {
method main (line 53) | static void main(String[] args) {
FILE: src/main/java/observer/observer2.java
type observer2 (line 8) | public interface observer2 {
type Kind (line 10) | enum Kind { BUY, SELL }
type BalanceObserver (line 13) | interface BalanceObserver {
method balanceChanged (line 14) | void balanceChanged(int newValue);
class StockExchange (line 17) | class StockExchange {
method StockExchange (line 22) | public StockExchange(BalanceObserver balanceObserver) {
method toString (line 26) | @Override
method setBalance (line 31) | public void setBalance(int balance) {
method setStockQuantity (line 35) | public void setStockQuantity(String tick, int quantity) {
method process (line 39) | public List<Order> process(List<? extends Order> orders) {
method main (line 64) | static void main(String[] args) {
FILE: src/main/java/observer/observer3.java
type observer3 (line 8) | public interface observer3 {
type Kind (line 10) | enum Kind { BUY, SELL }
type BalanceObserver (line 13) | interface BalanceObserver {
method balanceChanged (line 14) | void balanceChanged(int newValue);
type OrderObserver (line 17) | interface OrderObserver {
method rejected (line 18) | void rejected(Order order);
class StockExchange (line 21) | class StockExchange {
method StockExchange (line 26) | public StockExchange(BalanceObserver balanceObserver) {
method toString (line 30) | @Override
method setBalance (line 35) | public void setBalance(int balance) {
method setStockQuantity (line 39) | public void setStockQuantity(String tick, int quantity) {
method process (line 43) | public void process(List<? extends Order> orders, OrderObserver orde...
method main (line 66) | static void main(String[] args) {
FILE: src/main/java/railwayswitch/railwayswitch1.java
type railwayswitch1 (line 8) | public interface railwayswitch1 {
method findHostname (line 9) | static String findHostname() {
method main (line 38) | static void main(String[] args) {
FILE: src/main/java/railwayswitch/railwayswitch2.java
type railwayswitch2 (line 9) | public interface railwayswitch2 {
type Finder (line 10) | @FunctionalInterface
method find (line 12) | Optional<String> find();
method or (line 14) | default Finder or(Finder finder) {
method environment (line 19) | static Finder environment(String name) {
method systemProperty (line 23) | static Finder systemProperty(String name) {
method fileProperty (line 27) | static Finder fileProperty(Path path, String name) {
method findHostname (line 39) | static String findHostname() {
method main (line 47) | static void main(String[] args) {
FILE: src/main/java/state/state1.java
type state1 (line 6) | public interface state1 {
class Cart (line 11) | class Cart {
type State (line 12) | private enum State { CREATED, PAYED, SHIPPED }
method add (line 18) | public void add(Article article) {
method buy (line 25) | public void buy(CreditCard creditCard) {
method ship (line 33) | public void ship(Address address) {
method info (line 41) | public String info() {
method main (line 50) | static void main(String[] args){
FILE: src/main/java/state/state2.java
type state2 (line 6) | public interface state2 {
class Cart (line 11) | class Cart {
type State (line 12) | private sealed interface State {
method add (line 13) | default State add(Article article) {
method buy (line 16) | default State buy(CreditCard creditCard) {
method ship (line 19) | default State ship(Address address) {
method add (line 24) | @Override
method buy (line 30) | @Override
method ship (line 36) | @Override
method add (line 45) | public void add(Article article) {
method buy (line 48) | public void buy(CreditCard creditCard) {
method ship (line 51) | public void ship(Address address) {
method info (line 54) | public String info() {
method main (line 59) | static void main(String[] args){
FILE: src/main/java/templatemethod/templatemethod1.java
type templatemethod1 (line 3) | public interface templatemethod1 {
class DecoratedString (line 5) | abstract class DecoratedString {
method plainString (line 6) | protected abstract String plainString();
method loveString (line 8) | public String loveString() {
method main (line 13) | static void main(String[] args) {
FILE: src/main/java/templatemethod/templatemethod2.java
type templatemethod2 (line 6) | public interface templatemethod2 {
class DecoratedString (line 8) | class DecoratedString {
method DecoratedString (line 11) | public DecoratedString(Supplier<String> supplier) {
method loveString (line 15) | public String loveString() {
method main (line 20) | static void main(String[] args) {
FILE: src/main/java/typing/dynamictyping.java
type dynamictyping (line 3) | public interface dynamictyping {
class A (line 4) | class A {
method m (line 5) | public void m() {
class B (line 10) | class B {
method m (line 11) | public void m() {
method print (line 16) | static void print(Object o) throws Exception {
method main (line 20) | public static void main(String[] args) throws Exception {
FILE: src/main/java/typing/structuraltyping.java
type structuraltyping (line 3) | public interface structuraltyping {
type I (line 4) | interface I {
method m (line 5) | void m();
class A (line 8) | class A {
method m (line 9) | public void m() {
class B (line 14) | class B {
method m (line 15) | public void m() {
method print (line 20) | static void print(I i) {
method main (line 24) | static void main(String[] args) {
FILE: src/main/java/typing/subtyping.java
type subtyping (line 3) | public interface subtyping {
type I (line 4) | interface I {
method m (line 5) | void m();
class A (line 8) | class A implements I {
method m (line 9) | public void m() {
class B (line 14) | class B implements I {
method m (line 15) | public void m() {
method print (line 20) | static void print(I i) {
method main (line 24) | static void main(String[] args) {
FILE: src/main/java/visitor/visitor1.java
type visitor1 (line 5) | public interface visitor1 {
type Vehicle (line 6) | sealed interface Vehicle {
method accept (line 7) | <R> R accept(Visitor<? extends R> visitor);
method accept (line 10) | @Override
method accept (line 16) | @Override
type Visitor (line 22) | interface Visitor<R> {
method visitCar (line 23) | R visitCar(Car car);
method visitCarHauler (line 24) | R visitCarHauler(CarHauler carHauler);
method count (line 27) | static int count(Vehicle vehicle) {
method main (line 41) | static void main(String[] args) {
FILE: src/main/java/visitor/visitor2.java
type visitor2 (line 7) | public interface visitor2 {
type Vehicle (line 8) | interface Vehicle { }
class Visitor (line 12) | class Visitor<R> {
method when (line 15) | public <T> Visitor<R> when(Class<? extends T> type, Function<? super...
method call (line 19) | public R call(Object receiver) {
method count (line 26) | static int count(Vehicle vehicle) {
method main (line 33) | static void main(String[] args) {
FILE: src/main/java/visitor/visitor3.java
type visitor3 (line 5) | public interface visitor3 {
type Vehicle (line 6) | sealed interface Vehicle { }
method count (line 10) | static int count(Vehicle vehicle) {
method main (line 17) | static void main(String[] args) {
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (117K chars).
[
{
"path": ".github/workflows/maven.yml",
"chars": 422,
"preview": "on:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n build:\n runs-on: ubuntu-latest"
},
{
"path": ".gitignore",
"chars": 241,
"preview": "*.class\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.ear\n\n# virtual machine crash logs, se"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Rémi Forax\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 2199,
"preview": "# Design Patterns Reloaded\nImplementation of some design patterns in Java 21,\nsome of them are from the GoF, others are "
},
{
"path": "pom.xml",
"chars": 988,
"preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLoc"
},
{
"path": "src/main/java/abstractfactory/README.md",
"chars": 3966,
"preview": "# Abstract Factory\n\nLet's say we have a simple hierarchy of classes, with a `Bus` and a `Car` both implementing an inter"
},
{
"path": "src/main/java/abstractfactory/abstractfactory1.java",
"chars": 612,
"preview": "package abstractfactory;\n\npublic interface abstractfactory1 {\n sealed interface Vehicle {\n static Vehicle create(Str"
},
{
"path": "src/main/java/abstractfactory/abstractfactory2.java",
"chars": 1024,
"preview": "package abstractfactory;\n\nimport java.util.HashMap;\nimport java.util.function.Supplier;\n\npublic interface abstractfactor"
},
{
"path": "src/main/java/adapter/README.md",
"chars": 2001,
"preview": "# Adapter Pattern\n\nLet's say we have a simple interface like a `Logger`\n\n```java\ninterface Logger {\n void log(String me"
},
{
"path": "src/main/java/adapter/adapter1.java",
"chars": 255,
"preview": "package adapter;\n\npublic interface adapter1 {\n interface Logger {\n void log(String message);\n }\n\n static void sayH"
},
{
"path": "src/main/java/adapter/adapter2.java",
"chars": 695,
"preview": "package adapter;\n\npublic interface adapter2 {\n interface Logger {\n void log(String message);\n }\n \n enum Level { W"
},
{
"path": "src/main/java/builder/README.md",
"chars": 3023,
"preview": "# Builder pattern\n\nBy default, when creating an instance, the arguments of the constructor are passed in order,\nthe asso"
},
{
"path": "src/main/java/builder/builder1.java",
"chars": 1314,
"preview": "package builder;\n\npublic interface builder1 {\n record Spaceship(String name, String captain, int torpedoes, int length)"
},
{
"path": "src/main/java/builder/builder2.java",
"chars": 3577,
"preview": "package builder;\n\nimport java.io.Serializable;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodHand"
},
{
"path": "src/main/java/chainofresponsibility/README.md",
"chars": 3603,
"preview": "# Chain of Responsibility Pattern\n\nA chain of responsibility is a linked list of action that each take care of a part of"
},
{
"path": "src/main/java/chainofresponsibility/chainofresponsibility1.java",
"chars": 1120,
"preview": "package chainofresponsibility;\n\npublic interface chainofresponsibility1 {\n enum Level {\n INFO, WARNING, ERROR\n }\n "
},
{
"path": "src/main/java/chainofresponsibility/chainofresponsibility2.java",
"chars": 1096,
"preview": "package chainofresponsibility;\n\npublic interface chainofresponsibility2 {\n enum Level {\n INFO, WARNING, ERROR\n }\n "
},
{
"path": "src/main/java/command/README.md",
"chars": 7727,
"preview": "# Command Pattern\n\nA command pattern is the idea to consider an action as an object, so it can be as simple as\nusing a R"
},
{
"path": "src/main/java/command/command1.java",
"chars": 1786,
"preview": "package command;\n\nimport java.util.List;\n\npublic interface command1 {\n class Config {\n boolean showHidden = false;\n "
},
{
"path": "src/main/java/command/command2.java",
"chars": 2458,
"preview": "package command;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.function.C"
},
{
"path": "src/main/java/command/command3.java",
"chars": 2652,
"preview": "package command;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimpor"
},
{
"path": "src/main/java/decorator/README.md",
"chars": 4593,
"preview": "# The Decorator Pattern\n\nA decorator is a simple way to a dynamically enhance and existing behavior using composition.\n"
},
{
"path": "src/main/java/decorator/decorator1.java",
"chars": 1110,
"preview": "package decorator;\n\npublic interface decorator1 {\n interface Coffee {\n long cost();\n String ingredients();\n }\n\n "
},
{
"path": "src/main/java/decorator/decorator2.java",
"chars": 1296,
"preview": "package decorator;\n\npublic interface decorator2 {\n sealed interface Coffee {\n long cost();\n String ingredients();"
},
{
"path": "src/main/java/factory/README.md",
"chars": 2085,
"preview": "# Factory\n\nA factory abstracts the creation of instances. Conceptually, it's a function that returns a different instanc"
},
{
"path": "src/main/java/factory/factory1.java",
"chars": 686,
"preview": "package factory;\n\nimport java.util.List;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\npublic int"
},
{
"path": "src/main/java/factory/factory2.java",
"chars": 841,
"preview": "package factory;\n\nimport java.util.List;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\npublic int"
},
{
"path": "src/main/java/memoizer/README.md",
"chars": 3308,
"preview": "# Memoizer Pattern\n\nThe result of pure function with one parameter, i.e. a function that has its return value only depen"
},
{
"path": "src/main/java/memoizer/memoizer1.java",
"chars": 719,
"preview": "package memoizer;\n\nimport static java.util.stream.IntStream.range;\n\nimport java.util.HashMap;\n\npublic interface memoizer"
},
{
"path": "src/main/java/memoizer/memoizer2.java",
"chars": 937,
"preview": "package memoizer;\n\nimport static java.util.stream.IntStream.range;\n\nimport java.util.HashMap;\nimport java.util.function."
},
{
"path": "src/main/java/monad/README.md",
"chars": 3807,
"preview": "# Monad Pattern\n\nA monad is a class that encapsulates several disjoint states.\n\nLet say we have a `User` that comes from"
},
{
"path": "src/main/java/monad/monad1.java",
"chars": 507,
"preview": "package monad;\n\npublic interface monad1 {\n record User(String name, int age) { }\n\n static void validateUser(User user)"
},
{
"path": "src/main/java/monad/monad2.java",
"chars": 1260,
"preview": "package monad;\n\nimport monad.monad1.User;\n\nimport java.util.Objects;\nimport java.util.function.Predicate;\nimport java.ut"
},
{
"path": "src/main/java/monad/monad3.java",
"chars": 1684,
"preview": "package monad;\n\nimport java.util.Objects;\nimport java.util.function.Function;\nimport java.util.function.IntPredicate;\nim"
},
{
"path": "src/main/java/observer/README.md",
"chars": 8521,
"preview": "# Observer Pattern\n\nThe point of the observer pattern is to decouple two pieces of code by using an interface in the mid"
},
{
"path": "src/main/java/observer/observer1.java",
"chars": 2175,
"preview": "package observer;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.TreeMap;\nimport java.util.stream."
},
{
"path": "src/main/java/observer/observer2.java",
"chars": 2786,
"preview": "package observer;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.TreeMap;\nimport java.util.stream."
},
{
"path": "src/main/java/observer/observer3.java",
"chars": 2832,
"preview": "package observer;\n\nimport java.util.List;\nimport java.util.TreeMap;\nimport java.util.stream.Collectors;\nimport java.util"
},
{
"path": "src/main/java/railwayswitch/README.md",
"chars": 3197,
"preview": "# Railway Switch\n\nThe railway switch pattern, quoted by [Scott Wlaschin](https://fsharpforfunandprofit.com/rop/),\nis a w"
},
{
"path": "src/main/java/railwayswitch/railwayswitch1.java",
"chars": 900,
"preview": "package railwayswitch;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.u"
},
{
"path": "src/main/java/railwayswitch/railwayswitch2.java",
"chars": 1274,
"preview": "package railwayswitch;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.u"
},
{
"path": "src/main/java/state/README.md",
"chars": 6134,
"preview": "# The State Pattern\n\nWhen you have a workflow and you move from one state to another,\nit's often easier to read the code"
},
{
"path": "src/main/java/state/state1.java",
"chars": 1607,
"preview": "package state;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic interface state1 {\n record Article(String na"
},
{
"path": "src/main/java/state/state2.java",
"chars": 1916,
"preview": "package state;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic interface state2 {\n record Article(String na"
},
{
"path": "src/main/java/templatemethod/README.md",
"chars": 2028,
"preview": "# Template Method Pattern\n\nThe idea of the template method pattern is to decompose a problem in two parts, a generic reu"
},
{
"path": "src/main/java/templatemethod/templatemethod1.java",
"chars": 470,
"preview": "package templatemethod;\n\npublic interface templatemethod1 {\n\n abstract class DecoratedString {\n protected abstract S"
},
{
"path": "src/main/java/templatemethod/templatemethod2.java",
"chars": 553,
"preview": "package templatemethod;\n\nimport java.util.function.Supplier;\nimport java.util.function.UnaryOperator;\n\npublic interface "
},
{
"path": "src/main/java/typing/dynamictyping.java",
"chars": 419,
"preview": "package typing;\n\npublic interface dynamictyping {\n class A {\n public void m() { \n System.out.println(\"A::m\");\n "
},
{
"path": "src/main/java/typing/structuraltyping.java",
"chars": 387,
"preview": "package typing;\n\npublic interface structuraltyping {\n interface I {\n void m();\n }\n \n class A {\n public void m("
},
{
"path": "src/main/java/typing/subtyping.java",
"chars": 400,
"preview": "package typing;\n\npublic interface subtyping {\n interface I {\n void m();\n }\n \n class A implements I {\n public v"
},
{
"path": "src/main/java/visitor/README.md",
"chars": 5797,
"preview": "# The Visitor Pattern\n\nThe aim of Visitor Pattern is to be able to specify operations on a hierarchy of classes outside "
},
{
"path": "src/main/java/visitor/visitor1.java",
"chars": 1134,
"preview": "package visitor;\n\nimport java.util.List;\n\npublic interface visitor1 {\n sealed interface Vehicle {\n <R> R accept(Visi"
},
{
"path": "src/main/java/visitor/visitor2.java",
"chars": 1212,
"preview": "package visitor;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.function.Function;\n\npublic interface"
},
{
"path": "src/main/java/visitor/visitor3.java",
"chars": 562,
"preview": "package visitor;\n\nimport java.util.List;\n\npublic interface visitor3 {\n sealed interface Vehicle { }\n record Car() impl"
},
{
"path": "test/test.csv",
"chars": 25,
"preview": "1, 2, 4, 6.007\n4, 5, 6\n3\n"
}
]
About this extraction
This page contains the full source code of the forax/design-pattern-reloaded GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (106.4 KB), approximately 27.2k tokens, and a symbol index with 268 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.