Helps you to improve the readability of your code when you are using the Decorator pattern.
Makes your code much more readable!
For Gradle, add the following dependency in build.gradle
:
dependencies {
implementation 'io.github.chinhung:pointwave:1.0.0'
}
For Maven, add the following dependency in pom.xml
:
<dependency>
<groupId>io.github.chinhung</groupId>
<artifactId>pointwave</artifactId>
<version>1.0.0</version>
</dependency>
If you use other build tool, please visit Maven Central.
First, call the static method PointWave.decoratee
to initialize the decoratee. And then, call the method decorated
with the decorator function as the argument in lambda expression, one by one. The last step is to call complete
method, which returns the decorated object:
Object decorated = PointWave.decoratee(decoratee)
.decorated(decoratee -> decorator1Factory.create(decoratee, param1))
.decorated(decoratee -> Decorator2.createInstance(decoratee, param2))
.decorated(decoratee -> new Decorator3(decoratee, param3))
.complete();
- the decorator function is a function which receives the decoratee and returns the decorated object
- the decorator functions will be applied following the "First In, First Out" rule (in this example, the inner layer of the decorated object is
decoratee
, the second layer isDecorator1
, the third layer isDecorator2
and the outer layer isDecorator3
) - provides generic support
public class HelloWorld {
@Override
public String toString() {
return "Hello World!";
}
}
public class NameDecorator extends HelloWorld {
private final HelloWorld decoratee;
private final String name;
public NameDecorator(final HelloWorld decoratee, final String name) {
this.decoratee = decoratee;
this.name = name;
}
@Override
public String toString() {
return decoratee.toString() + " " + name + "!";
}
}
public class TodayDecorator extends HelloWorld {
private final HelloWorld decoratee;
private final String today;
public TodayDecorator(final HelloWorld decoratee, final String today) {
this.decoratee = decoratee;
this.today = today;
}
@Override
public String toString() {
return decoratee.toString() + " Today is " + today + "!";
}
}
HelloWorld decorated = PointWave.decoratee(new HelloWorld())
.decorated(decoratee -> new NameDecorator(decoratee, "John Doe"))
.decorated(decoratee -> new TodayDecorator(decoratee, "Friday"))
.complete();
assertEquals("Hello World! John Doe! Today is Friday!", decorated.toString());
The code is not readable if you decorate the object manually in the following coding style:
- a single line code
Object decorated = new Decorator3(Decorator2.createInstance(decorator1Factory.create(decoratee, param1), param2), param3);
- deeply nested code
Object decorated = new Decorator3(
Decorator2.createInstance(
decorator1Factory.create(
decoratee,
param1
),
param2
),
param3
);
We could improve the readability of the code by using a temporary local variable:
Object decorated = decoratee;
decorated = decorator1Factory.create(decorated, param1);
decorated = Decorator2.createInstance(decorated, param2);
decorated = new Decorator3(decorated, param3);
However, the code style is imperative.
The code style is continuous and similar to the builder pattern, which is more meaningful. Also, an appropriately designed api brings more intention of the code to the reader:
Object decorated = PointWave.decoratee(decoratee)
.decorated(decoratee -> decorator1Factory.create(decoratee, param1))
.decorated(decoratee -> Decorator2.createInstance(decoratee, param2))
.decorated(decoratee -> new Decorator3(decoratee, param3))
.complete();
Even more, the intention would be more clear if the decorator functions were named appropriately:
Function withDecorator1 = decoratee -> decorator1Factory.create(decoratee, param1);
Function withDecorator2 = decoratee -> Decorator2.createInstance(decorated, param2);
Function withDecorator3 = decoratee -> new Decorator3(decorated, param3);
Object decorated = PointWave.decoratee(decoratee)
.decorated(withDecorator1)
.decorated(withDecorator2)
.decorated(withDecorator3)
.complete();
- abstract
Decoratee
as interface - refactor the code to functional style
- use
Github Actions
to build and test the code - write
Javadoc
- use
Git Flow
in this project - incremental delivery