In this video, Venkat Subramaniam revisits a few patterns from the book by the Gang of Four and adapts them to the new features in Java.
https://www.youtube.com/watch?v=V7iW27F9oog
- Patterns And Anti-Patterns
- Factory Pattern
- Strategy Pattern
- Decorator Pattern
- Execute Around Pattern
Optional - Patterns and Anti-Patterns
Using an Optional as an input to a method is an anti-pattern as it involves writing extra code when calling that method
public void setSomething(Optional<Integer> number) {
...
}
setSomething(Optional.empty())
setSomething(Optional.of(...)
Similarly, returning an Optional is not fool-proof, as a method may return a null.
public Optional<> getSomething() {
return null;
}
Factory Pattern
Java interfaces allow defining a default implementation of a method that can invoke the interface method.
public interface Pet {
public String getName();
default String playWithPet() {
return "Playing with " + getName();
}
}
Strategy Pattern
The example used here involves
- using a Lambda to define strategy and then
- shipping it into a method.
public Integer addNumbers(List<Integer> numbers, Function<Integer,Boolean> strategy) {
Integer result = 0;
for (var i: numbers) {
if (strategy.call(i))
result += i;
}
return result;
}
The Function
object supports andThen
, and it is possible to do function composition, using the output of the first function as the input to the second function.
Function incrementANumber = n -> n + 1;
Function doubleANumber = n -> n * 2;
Function incrementAndDoubleANumber = incrementANumber.andThen(doubleANumber);
Decorator Pattern
The original Decorator pattern involves wrapping an object with another object (BufferedInputStream).
With a stream
, it is possible to compose multiple lambda
functions into a single function using reduce()
, as illustrated in the following example -
public Camera(Function<Color,Color>... filters) {
filter = Stream.of(filters)
.reduce(Function.identity(), Function::andThen);
}
Execute Around Pattern
The Execute Around Pattern is helpful if you want certain logic executed before and after your code.
The example in the video demonstrates how resource cleanup can happen deterministically.
public Class Resource {
public Resource op1() {...}
public Resource op2() {...}
private Resource() {...}
private void close() {...}
public static void use(Consumer<Resource> block) {
Resource resource = new Resource();
try {
block.accept(resource);
} finally {
resource.close();
}
}
}
Resource.use(resource -> resource.op1().op2());
The above example (in my opinion) is idealistic and in reality, might be a little tricky to achieve. It might make sense in a new implementation or code that easily lends itself to refactoring.