Reputation: 2314
I want to refactor template method using java 8 new default method. Say I have a flow of process define in abstract class:
public abstract class FlowManager{
public void startFlow(){
phase1();
phase2();
}
public abstract void phase1();
public abstract void phase2();
}
and I have few subclass's that extend the above flow manager and each subclass implements its own phase1
and phase2
mathod. I wonder if its make any sense to refactor the code to an interface like this:
public interface FlowManager{
public default startFlow(){
this.phase1();
this.phase2();
}
public void phase1();
public void phase2();
}
What do you think?
Upvotes: 5
Views: 9976
Reputation: 2076
It took some time to get my head around the implementation of Template method in Java 8, It is like magic :) There are some differences in the way we implement it in Java 8.
1- The parent class does not define the methods(which will be implemented later in the child class) in it's body, It defines them as parameters in the final method signature.
2- Based the above,The child class does not have to provide the implementation in it's body, It will offer the implementation during the instantiation.
import java.util.function.Consumer;
public abstract class FlowManager<T> {
public final void startFlow(T t,
Consumer<T> phase1,
Consumer<T> phase2){
phase1.accept(t);
phase2.accept(t);;
}
}
Implementation
public class FlowManager2<T>
extends FlowManagerJava8<String>{
}
Main class
import java.util.function.Consumer;
public class Main {
public static void main(String args[]){
new FlowManager2<String>().startFlow("Helo World",
(String message)->System.out.println("Phase 1 : "+ message),
(String message)->System.out.println("Phase 2 : "+ message));
Consumer<String> phase1 =
(String message)-> System.out.println("Phase 1 : "+ message);
Consumer<String> phase2 =
(String message)-> System.out.println("Phase 2 : "+ message);
new FlowManager2<String>().startFlow("Helo World",
phase1,
phase2);
}
}
Upvotes: 0
Reputation: 21
//design Template class
public class Template {
protected interface MastSuppler{
List<Mast> apply(int projectId);
}
protected interface Transform<T>{
List<T> apply(List<Mast> masts);
}
protected interface PropertiesConsumer<T>{
void apply(List<T> properties);
}
public <T> void template(int projectId, MastSuppler suppler, Transform<T> transform, PropertiesConsumer<T> consumer){
System.out.println("projectId is " + projectId);
//1.List<Mast> masts = step1(int projectId);
List<Mast> masts = suppler.apply(projectId);
//2.List<T> properties = step2(List<Mast> masts)
List<T> properties = transform.apply(masts);
//3.use or consume these properties(print to console ,save to datebase)
consumer.apply(properties);
}
}
//use with client
public class Mast {
public static void main(String[] args) {
//1.save to db
new Template().template(1,
projectId->getMastsfromMongo(projectId),
masts-> masts.stream().map(mast->mast.getName()).collect(Collectors.toList()),
names->System.out.println("save names to db "+ names));
//new Template(1, id->Arrays, );
//2.print to console
new Template().template(2,
projectId->getMastsSomewhere(projectId),
masts-> masts.stream().map(mast->mast.getLat()).collect(Collectors.toList()),
names->System.out.println("print lons to console "+ names));
}
private static List<Mast> getMastsfromMongo(int projectId){
Mast m1 = new Mast("1", 110, 23);
Mast m2 = new Mast("2", 111, 13);
return Arrays.asList(m1, m2);
}
private static List<Mast> getMastsSomewhere(int projectId){
Mast m1 = new Mast("3", 120, 53);
Mast m2 = new Mast("4", 121, 54);
return Arrays.asList(m1, m2);
}
private String name;
private double lon;
private double lat;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getLon() {
return lon;
}
public void setLon(double lon) {
this.lon = lon;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public Mast(String name, double lon, double lat) {
super();
this.name = name;
this.lon = lon;
this.lat = lat;
}
}
Upvotes: 2
Reputation: 100349
In addition to the earlier answers note that there are more possibilities. First is to separate the template method into its own class:
public interface Flow {
void phase1();
void phase2();
}
public final class FlowManager {
private final Flow flow;
public FlowManager(Flow flow) {
this.flow = flow;
}
public void startFlow() {
flow.phase1();
flow.phase2();
}
}
If you are already using FlowManager.phaseX
methods you may make it implementing the Flow
interface as well:
public final class FlowManager implements Flow {
private final Flow flow;
public FlowManager(Flow flow) {
this.flow = flow;
}
public void startFlow() {
flow.phase1();
flow.phase2();
}
@Override
public void phase1() {
flow.phase1();
}
@Override
public void phase2() {
flow.phase2();
}
}
This way you explicitly signal that users have to implement the Flow
interface, but they cannot change the startFlow
template method as it's declared in final class.
Java 8 adds a new functional pattern to solve your problem:
public final class FlowManager {
private final Runnable phase1;
private final Runnable phase2;
public FlowManager(Runnable phase1, Runnable phase2) {
this.phase1 = phase1;
this.phase2 = phase2;
}
public void startFlow() {
phase1.run();
phase2.run();
}
public void phase1() {
phase1.run();
}
public void phase2() {
phase2.run();
}
}
Well, this code works even prior to Java 8, but now you can create the FlowManager
using lambdas or method references which is much more convenient.
You can also combine the approaches: define the interface and provide a way to construct it from lambdas:
public interface Flow {
void phase1();
void phase2();
static Flow of(Runnable phase1, Runnable phase2) {
return new Flow() {
@Override
public void phase1() {
phase1.run();
}
@Override
public void phase2() {
phase2.run();
}
};
}
}
The Collector
interface in Java 8 is implemented in similar way. Now depending on the users preference they can either implement the interface directly or use Flow.of(...)
and pass the lambdas or method references there.
Upvotes: 3
Reputation: 132610
Using an interface with a default method to implement the template method pattern seems suspicious to me.
A default method is usually (though not always) intended to be overridden by implementors. If an interface's default method were used as a template method, the overriding method would be susceptible to programming errors such as not calling the super
method, calling it at the wrong time, changing the order in which the phases are called, etc. These are all programming errors that the template method pattern is intended to avoid.
Usually the template method is not intended to be overridden. In Java classes this can be signaled by making the method final
. Interfaces cannot have final methods; see this question for rationale. Thus it's preferable to implement the template method pattern using an abstract class with a final method as the template.
Upvotes: 15
Reputation: 12403
Both approaches would work.
Which one to use depends a lot on what other functionality your FlowManager
will have and how you will use it later.
An abstract class would allow you to define non-static fields if you then need to model some state as well. It will also allow to have private or protected methods.
On the other hand, an interface would make it easier to implement by non-related classes as you would not be constrained to single inheritance.
Java's tutorial summarizes it pretty well here in the "Abstract Classes Compared to Interfaces" section:
http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html
Upvotes: 0