Java 8 Lambda-Enabled Design patterns
Overview
Java8 is providing a new way to redesign our existing code into a simple and more elegant one. The good old days (before java 8) of java version need more code to express even simple design patterns. In many cases, the language changes in Java 8 are the driving factor behind the thinking change. We have our new boss in our town Java 14; Still, Usage of Java 8 needs more fluent then now.
The critical design tool for software development is a mind well educated in design principles. It is not technology.
— Craig Larman
Java 8 highlights
Java 8 has several features added and improved as it is a major release, I have listed a few here.
- Streams
- Lambdas
- Functional Interface
- Consumer / Supplier
More reading about features refer to Release notes
Lambda
I am going to drive you through to rewrite a few of well-known Gang of four design patterns. Presumably, this would be useful to get a shift in our thinking to write better implementation.
Builder and Factory design patterns are more popular ones under the Creational design pattern. I am taking these two patterns to showcase Consumer & Supplier function’s practical usage. Usage of lambda expression not just limited to these design patterns, if there is good understanding then changes can be applied to all other patterns too. Lambdas are basically lazy, so it is good to learn and use them in an appropriate place. Of course, no practice is ever the best practice forever.
Note :
- The factory design pattern is written using java8 Supplier
- The builder design pattern is written using java 8 Consumer
Refer design pattern in java implementation
Lambdas & Functional Interfaces are another belt of a professional Java developer, no different from an interface or a class.
Let’s look at a concrete example of the Builder pattern and see how it improves with lambda expressions
Builder design pattern
Concrete example
package com.ran;class Person {
private String firstName;
private String lastName; public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}}class PersonBuilder { private String firstName;
private String lastName; public PersonBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
} public PersonBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
} public Person build() {
return new Person(firstName, lastName);
}}public class BuilderPattern {
public static void main(String[] args) {
Person person = new PersonBuilder().withFirstName("Super").withLastName("Hero").build(); }
}
The above code is valid, but there is morewith**
method need to be added in the builder class when new properties introduced. Lambda solves this problem and provides as Groovy like syntax to java builder pattern.
Remove withFirstName
,withLastName
and replace the below code in the PersonBuilder class and change builder properties to the public. This saves a lot of lines in the builder class.
public PersonBuilder with(
Consumer<PersonBuilder> builderFunction) {
builderFunction.accept(this);
return this;
}
Final code
package com.ran;import java.util.function.Consumer;import java.util.function.Consumer;class Person {
private String firstName;
private String lastName; public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}}
class PersonBuilder { public String firstName;
public String lastName;
public PersonBuilder with(
Consumer<PersonBuilder> builderFunction) {
builderFunction.accept(this);
return this;
}
public Person build() {
return new Person(firstName,lastName);
}
}public class BuilderPattern{
public static void main(String[] args) {
Person person = new PersonBuilder()
.with($ -> $.firstName = "Super")
.with($ -> $.lastName = "hero")
.build();
}
}
Factory design pattern
The factory design pattern is written using lambda
package com.ran;import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;enum OS {
ANDROID, IOS;
}interface Mobile {}class Android implements Mobile {}class IOS implements Mobile {}class MobileFactory {
static final Map<OS, Supplier<Mobile>> map = new EnumMap<>(OS.class); //Enum map
// Store references of different OS
static {
map.put(OS.ANDROID, Android::new); // :: method reference operator
map.put(OS.IOS, IOS::new);
} public Mobile getMobile(OS type) {
return map.get(type).get();
}
}public class FactoryPattern{
public static void main(String[] args) {
MobileFactory mobileFacotry = new MobileFactory();
Mobile mobile = null;
mobile = mobileFacotry.getMobile(OS.ANDROID);
mobile = mobileFacotry.getMobile(OS.IOS);
System.out.println(mobile.getClass().getName());
}
}
P.S: I did not go deep dive into what is Lambdas, Function, Consumer, Supplier, Method reference operator, Referential transparency, and so on. Leaving it the readers to learn more about it.
Refer below link for more reading :
Book : Java 8 Lambdas by Richard Warburton Link : Chapter 8. Design and Architectural Principles