Design Patterns Revisited - Venkat Subramaniam


Photo by Matej: https://www.pexels.com/photo/ceiling-776336/

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.