Templating method pattern template using Java 8

I want to refactor a template method using the new default java 8. Let's say that a process flow is defined in an abstract class:

public abstract class FlowManager{
    public void startFlow(){
        phase1();
        phase2();
    }
    public abstract void phase1();
    public abstract void phase2();
}

      

and I have several subclasses that extend the above thread manager and each subclass implements its own phase1

and phase2

mathod. I wonder if it makes 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?

+3


source to share


5 answers


Using an interface with a standard method to implement a template method pattern seems suspicious to me.

The default method usually (though not always) needs to be overridden by developers. If the default interface method was used as the template method, the override method would be susceptible to programming errors, such as not calling the method super

, calling it at the wrong time, changing the order in which these phases are called, etc. These are all programming errors that should be avoided by the templating method pattern.



Typically a template method is not meant to be overridden. In Java classes, this can be signaled by creating a method final

. Interfaces cannot have final methods; see this question for a rationale. Thus, it is preferable to implement the method template pattern using an abstract class with the final method as the template.

+7


source


In addition to the earlier answers, please note that there are more possibilities. First, you need 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 methods FlowManager.phaseX

, you can make it implement the interface Flow

:

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 indicate that users need to implement the interface Flow

, but they cannot change the template method startFlow

as it was declared in the 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 even works up to Java 8, but now you can create FlowManager

using lambdas or method references, which is much more convenient.

You can also combine approaches: define an interface and provide a way to create 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 interface Collector

in Java 8 is implemented in a similar way. Now, depending on user preference, they can either implement the interface directly, or use Flow.of(...)

and pass lambdas or method references there.

+2


source


// 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;
        }


}

      

+2


source


Both approaches will work.

Which one to use depends on what other features yours will have FlowManager

and how you will use it later.

An abstract class will allow you to define non-static fields if you need to model some state as well. This will also allow you to have private or protected methods.

On the other hand, an interface will make it easier to implement by unrelated classes, since you don't have to be limited to single inheritance.

The Java Tutorial describes it well here under "Abstract Classes vs. Interfaces":

http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html

0


source


It took a while to get an idea of ​​the implementation of the Template method in Java 8. It seems like magic :) There are some differences in how we implement it in Java 8.

1- The parent class does not define methods (which will be implemented later in the child class) in its body, It defines them as parameters in the final method signature.

2- Based on the above, the child class does not have to provide implementation in it, it will propose the implementation at creation time.

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);

}
}

      

0


source







All Articles