Template method design pattern using Java 8
Asked Answered
L

5

5

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?

Ligialignaloes answered 29/6, 2015 at 20:48 Comment(0)
V
15

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.

Vector answered 30/6, 2015 at 1:26 Comment(8)
Whats your take on the fact the Template pattern forcibly locks your class from extending any more classes. If you had to chose between blocking a programmer from overriding a method vs locking your class from further extension, what would you go with?Pluckless
@CKing I wouldn't do either. Instead of having the client provide its implementations via overriding, I'd have it pass functions (lambdas) as parameters, which the template method would call at the right time.Vector
@StaurtMarks In that case, would you still call it the template pattern? Wouldn't it just be a standard functional programming use case then? My question to you was specifically for the template pattern shown in the question.Pluckless
@CKing Sure, this is probably an idiomatic functional programming technique. Is it still a design pattern? I guess it depends on whether you think of design patterns as being specifically about objects, or whether patterns are applicable even if you're not using objects to represent them.Vector
@SuartMarks I get your point. Leaving all that discussion aside and only talking about the direct question that you have answered, it seems that you would chose giving up the opportunity to extend any further classes over giving up the ability to lock a method from being overriden.Pluckless
@CKing It's hard to sign up to such a pronouncement, since we're talking in the abstract here. Given a real-world problem, different tradeoffs might apply. That said, my answer is mostly a reaction to what I see as misuse of default methods. When they were introduced, a common reaction was "we don't need abstract classes anymore" but I don't think this is correct. See these answers for instance. https://mcmap.net/q/522429/-usage-of-multiple-inheritance-in-java-8 https://mcmap.net/q/1921060/-does-java-have-plan-that-default-method-java8-substitute-for-abstract-classVector
@StaurtMarks That was a good read and my thoughts are on similar lines when it comes to interfaces and abstract classes. But like I said, locking a class to extension is a big one for me. Is it time to get rid of old habits and start rethinking these patterns? Would a better answer to this question be the one that talks about how one can simply implement this pattern as a template method wherever required?Pluckless
@CKing Is it time to get rid of old habits? Possibly. There's a lot of material out there on objected-oriented vs functional programming, or FP "styles" of programming. Certainly it's much more feasible to write FP style programs in Java 8 than it has been in the past.Vector
G
3

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.

Gati answered 30/6, 2015 at 4:21 Comment(0)
M
2

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


}
Marlea answered 27/11, 2015 at 9:42 Comment(0)
E
0

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

Ethicize answered 29/6, 2015 at 21:5 Comment(0)
D
0

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

}
}
Darnley answered 14/3, 2017 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.