使用Java 8的模板方法设计模式

Template method design pattern using Java 8

我想用Java 8新的默认方法重构模板方法。假设我在抽象类中有一个流程定义:

1
2
3
4
5
6
7
8
public abstract class FlowManager{
    public void startFlow(){
        phase1();
        phase2();
    }
    public abstract void phase1();
    public abstract void phase2();
}

我有几个扩展上述流管理器的子类,每个子类实现自己的phase1phase2mathod。我想知道将代码重构为这样的接口是否有意义:

1
2
3
4
5
6
7
8
public interface FlowManager{
    public default startFlow(){
        this.phase1();
        this.phase2();
    }
    public void phase1();
    public void phase2();
}

你怎么认为?


使用带有默认方法的接口来实现模板方法模式在我看来是可疑的。

默认方法通常(尽管不总是)被实现者覆盖。如果将接口的默认方法用作模板方法,则重写方法可能会受到编程错误的影响,例如不调用super方法、在错误的时间调用它、更改调用阶段的顺序等。这些都是模板方法模式要避免的编程错误。

通常不打算重写模板方法。在Java类中,可以通过使用EDCOX1(2)方法来发出信号。接口不能有最终的方法;请参阅此问题了解基本原理。因此,最好使用一个抽象类来实现模板方法模式,最后一个方法作为模板。


除了前面的答案,注意还有更多的可能性。首先是将模板方法分成自己的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
    }
}

如果您已经在使用FlowManager.phaseX方法,您也可以让它实现Flow接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
    }
}

通过这种方式,您可以明确地表示用户必须实现Flow接口,但不能像在最终类中声明的那样更改startFlow模板方法。

Java 8增加了一个新的功能模式来解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
    }
}

这个代码甚至在Java 8之前工作,但是现在你可以使用lambdas或方法引用来创建EDCOX1 0。

您还可以组合这些方法:定义接口并提供一种从lambda构造它的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
            }
        };
    }
}

Java 8中的EDCOX1×8接口以类似的方式实现。现在,根据用户的偏好,他们要么直接实现接口,要么使用Flow.of(...),并在那里传递lambda或方法引用。


//设计模板类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
    }  

}

//与客户端一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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;
        }


}

我花了一些时间来研究Java 8中模板方法的实现,它就像魔术一样:在Java 8中实现它的方式有些不同。

1-父类没有在其主体中定义方法(稍后将在子类中实现),它在最终方法签名中将它们定义为参数。

2-基于以上内容,子类不必在其主体中提供实现,它将在实例化期间提供实现。

1
2
3
4
5
6
7
8
9
10
11
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);;
}
}

实施

1
2
3
4
public class FlowManager2<T>
        extends FlowManagerJava8<String>{

}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);

}
}

这两种方法都有效。

使用哪一个功能在很大程度上取决于您的FlowManager将具有哪些其他功能,以及以后如何使用它。

如果需要对某些状态建模,抽象类将允许您定义非静态字段。它还允许使用私有或受保护的方法。

另一方面,接口将使非相关类更容易实现,因为您不会被约束为单个继承。

Java的教程总结了它在"抽象类与接口"部分的比较好:

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