Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template method design pattern using Java 8

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?

like image 808
user1409534 Avatar asked Dec 04 '22 03:12

user1409534


2 Answers

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.

like image 130
Stuart Marks Avatar answered Dec 06 '22 18:12

Stuart Marks


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.

like image 28
Tagir Valeev Avatar answered Dec 06 '22 20:12

Tagir Valeev