Java设计模式之模板模式

概论

什么是模板模式呢?模板模式是这么定义的:定义一个操作中的算法的框架,而将这个框架中的某一些步骤延迟到子类中。使得子类可以不改变一个算法的结构。而能够重新定义该算法的某一些特定的步骤。

模板模式示例

举个例子,假如我们工作中的系统需要和许多的外部系统做交互时。由于外部系统各种各样,因此我们在发起请求时,处理方式会有所不多,有些是http请求,有些的webservice请求。或者是其他请求。然而真正在做的应该分成4个步骤。

1.参数校验。

2.封装对外的请求参数。

3.对外提交请求。

4.后置处理,例如记录操作日志。

ok,首先我们需要拥有一个抽象模板类用来定义这些步骤。

 1 package com.example.pattern.template;
 2 
 3 public abstract class AbstractProccessor {
 4 
 5     public boolean validate(ExampleContext context) {
 6         if (context == null) {
 7             return false;
 8         }
 9 
10         return true;
11     }
12 
13     public abstract void prepare(ExampleContext context);
14 
15     public abstract void proccess(ExampleContext context);
16 
17     public abstract void after(ExampleContext context);
18 
19 
20     
21     public void run (ExampleContext context) {
22 
23         validate(context);
24 
25         prepare(context);
26 
27         proccess(context);
28 
29         after(context);
30 
31     }
32 
33 }

 抽象模板类AbstractProccessor 对外提供一个算法,也就是run方法。 这个算法中包含了参数校验,前置处理,提交请求,后置处理4个步骤。由于参数校验是最基本也是最公共的校验,因此在这个抽象模板中直接实现参数校验的具体方法。其他三个方法就以抽象方法的形态存在。

现在我们新增一个具体的模板类。比如我们就叫HttpProccessor。

 1 package com.example.pattern.template;
 2 
 3 public class HttpProccessor extends AbstractProccessor {
 4 
 5     @Override
 6     public void prepare(ExampleContext context) {
 7 
 8         System.out.println("http 前置处理");
 9 
10     }
11 
12     @Override
13     public void proccess(ExampleContext context) {
14 
15         System.out.println("http 提交请求");
16 
17     }
18 
19     @Override
20     public void after(ExampleContext context) {
21         
22         System.out.println("http 后置处理");
23 
24     }
25 }

我们继续新增一个模板类,命名为OtherProccessor。

 1 package com.example.pattern.template;
 2 
 3 public class OtherProccessor extends AbstractProccessor {
 4 
 5     @Override
 6     public void prepare(ExampleContext context) {
 7 
 8         System.out.println("other 前置处理");
 9 
10     }
11 
12     @Override
13     public void proccess(ExampleContext context) {
14 
15         System.out.println("other 提交请求");
16 
17     }
18 
19     @Override
20     public void after(ExampleContext context) {
21 
22         System.out.println("other 后置处理");
23 
24     }
25 }

最后我们提供一个业务场景类Client

package com.example.pattern.template;

public class Client {

    public static void main(String[] args) {
        
        AbstractProccessor proccessor = new HttpProccessor();

        proccessor.run(new ExampleContext());
        
        
    }
}

执行结果如下所示:

1 http 前置处理
2 http 提交请求
3 http 后置处理

模板方法的精髓在于定义一个算法,这个算法中包含一系列的步骤,这些步骤如果是公共的步骤,可以提取在抽象模板类中实现,如果是模板个性化的行为,可以延迟到子类去实现。 

现在再提出一个问题。我想在有些模板中不使用后置处理,有些模板中使用后置处理,也就是要不要后置处理,交给业务场景来确定,那该怎么办呢?我们在抽象模板类中增加一个方法标记是否需要后置处理。

 1 package com.example.pattern.template;
 2 
 3 public abstract class AbstractProccessor {
 4 
 5     public boolean validate(ExampleContext context) {
 6         if (context == null) {
 7             return false;
 8         }
 9 
10         return true;
11     }
12 
13     public abstract void prepare(ExampleContext context);
14 
15     public abstract void proccess(ExampleContext context);
16 
17     public abstract void after(ExampleContext context);
18 
19     protected boolean needAfterProccessing () {
20         return true;
21     }
22 
23 
24 
25     public void run (ExampleContext context) {
26 
27         validate(context);
28 
29         prepare(context);
30 
31         proccess(context);
32 
33         if(needAfterProccessing()) {
34             after(context);
35         }
36     }
37 
38 }

 needAfterProccessing方法子类可以实现重写。代码如下所示:

 1 package com.example.pattern.template;
 2 
 3 public class HttpProccessor extends AbstractProccessor {
 4 
 5     protected boolean needAfterProccessing = false;
 6 
 7     @Override
 8     public void prepare(ExampleContext context) {
 9 
10         System.out.println("http 前置处理");
11 
12     }
13 
14     @Override
15     public void proccess(ExampleContext context) {
16 
17         System.out.println("http 提交请求");
18 
19     }
20 
21     @Override
22     public void after(ExampleContext context) {
23 
24         System.out.println("http 后置处理");
25 
26     }
27 
28 
29     @Override
30     protected boolean needAfterProccessing() {
31         return needAfterProccessing;
32     }
33 
34 
35     public boolean isNeedAfterProccessing() {
36         return needAfterProccessing;
37     }
38 
39     public void setNeedAfterProccessing(boolean needAfterProccessing) {
40         this.needAfterProccessing = needAfterProccessing;
41     }
42 }

 我们再来修改一下客户端,设置不需要进行后置处理:

 1 package com.example.pattern.template;
 2 
 3 public class Client {
 4 
 5     public static void main(String[] args) {
 6 
 7         AbstractProccessor proccessor = new HttpProccessor();
 8 
 9         ((HttpProccessor) proccessor).setNeedAfterProccessing(false);
10 
11         proccessor.run(new ExampleContext());
12 
13 
14     }
15 }

运行结果如下所示:

1 http 前置处理
2 http 提交请求

通过以上的方式,我们采用把相同的代码(都要进行判断是不是需要后置处理)这个判断的抽象动作封装在抽象类中,而在子类再去约束他具体的行为,这个函数就叫作钩子函数

模板模式的优点

1.封装不变部分,扩展可变部分。

2.提取公共部分。

3.引入了钩子函数,行为由父类控制,子类实现。

模板模式的缺点

抽象类在一般情况下的作用是用来定义抽象行为,而模板模式却是将算法的处理步骤实现在抽象类中,抽象类中定义了抽象方法,交给子类去实现,而子类的实现方式会影响到父类的算法。在代码阅读上对技术有一定的要求。

模板模式的使用场景

1.多个子类有共有的方法,同时基本的处理逻辑相同。

2.重要复杂的算法,可以把算法设计成模板模式,算法中的变化的步骤定义为抽象步骤。

3.重构时,把相同或者相似的代码提取到抽象类中,并且使用钩子函数来约束父类的算法。

一般对于Java的开发者而言,对HttpServlet都很熟悉。Servlet使用来浏览器和tomcat服务器沟通的桥梁,而这个桥梁的简单封装就是HttpServlet。每一次的请求都会去调用service方法,而service方法中根据请求的参数,调用不同的方法。例如doGet或者doPost。而doGet或者doPost都是在子类中定义定位,因此这两个函数也叫作钩子函数,即子类改变父类的行为。这也是对设计模式,模板模式的最佳应用。

原文地址:https://www.cnblogs.com/sunshine798798/p/10026867.html