3

设计模式之(13)--模板方法模式 - 一只烤鸭朝北走

 1 year ago
source link: https://www.cnblogs.com/wha6239/p/17133046.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

设计模式之(13)--模板方法模式

  今天我们来学习下模板方法设计模式。

  模板方法(Template Method Pattern):抽象的父类中定义一个操作中算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤。简单地讲,就是“父类的模板方法定义不变的流程,子类重写流程中的方法”。

  类图如下所示:

  

1139198-20230223225609658-1104585739.png

  在上面UML类图中我们定义了两种角色:

  1、抽象模板(AbstractSuperClass):抽象模板类,定义了一套算法框架/流程;

  2、具体实现(ConcreteClass):具体实现类,对算法框架/流程的某些步骤进行了实现。

  我们在上面两种角色中定义的方法可以分为两类:

  第一类:诸如baseOperation()或者customOperation()方法,我们认为是基本操作方法,定义类算法的某些步骤,可以交由子类去实现,按照“迪米特法则”,这些方法应根据需要保持最小的可见性,通常我们将不需要重写的方法权限定义为private,而需要重写的方法访问权限设置为protected。

  第一类:templateMethod(),我们称之为模板方法,可以有一个或多个,定义算法的骨架,实现对基本方法的调用,完成固定的逻辑。为了防止恶意操作,这些方法通常定义为final,不容许子类去重写。

  这里有个小技巧:我们尽量不要将这些需要子类实现基本操作方法设置为抽象方法,这是因为有些具体子类实现并不需要重写这种方法,在抽象模板类中将这些基本方法定义为abstract强迫所有子类去实现就有点强人所难了。但是有些基本操作的方法,在某些特定的模板方法中去调用必须交由子类去实现,我们可以在这个方法定义中抛出一个异常,强迫子类去实现。这类设计在JDK源码中很常见,如AbstractQueuedSynchronizer类中的acquire(int)、acquireShared(int)、release(int)、releaseShared(int)就是定义的模板方法,而tryAcquire()、tryAcquireShared()、tryReleaseShared()就是基本方法,在AQS中都没有提供具体的实现逻辑,只是抛出了UnsupportedOperationException()异常,这就需要类似ReentrantLock.Sync、Semaphore.Sync这些子类去按需实现了。

  故事背景:我司有款产品,被几个第三方开发商看中,与我司签订协议,需要使用他们自身的帐号体系登录我们的产品直接使用,我司领导为了拿下这些单子,给我们部门下达了这项任务。经过双方的友好协商,第三方提供用户的验证接口,我们只需要去对接就行了。系统用户登录验证的流程基本都是一致的,只是不同的对接方提供的用户验证接口和返回结果不同,我们只需要将用户验证的骨架流程定义出来,然后针对不同对接渠道定制开发某些特定的步骤就可以了,这也符合程序设计的“开闭原则”。通过上面分析,我脑子里迅速地过了下,就想到了模版方法设计模式。

  以下就是一个简单的测试demo:

 1 package cn.com.pep.model.template;
 2 /**
 3  * @ClassName: AbstractSuperClass
 4  * @Description: 验证用户的模版类
 5  * @author: wwh
 6  * @date: 2023年2月24日 下午1:45:06
 7  */
 8 public abstract class AbstractUserCheck {
 9     
10     /**
11      * @Title: domainCheck
12      * @Description: 检测第三方提供的域名是否可用
13      * @param domain
14      * @return 
15      * String 返回类型
16      */
17     private String domainCheck(Object domain){
18         //伪代码
19         System.err.println("域名可访问...");
20         return "ok";
21     };
22     
23     /**
24      * @Title: tryCheckUser
25      * @Description: 不同渠道用户验证的逻辑,强制子类趋势线
26      * @param obj
27      * @return Object 返回类型
28      */
29     protected Object tryCheckUser(Object ...obj) {
30         throw new UnsupportedOperationException();
31     }
32     
33     /**
34      * @Title: wrapperResult
35      * @Description: 对各个渠道的验证结果进行转换,包装为系统的响应结果
36      * @param response
37      * @return 
38      * Map<String,Object> 返回类型
39      */
40     protected Object wrapperResult(Object ...obj) {
41         throw new UnsupportedOperationException();
42     }
43     
44     /**
45      * 
46      * @Title: userCheck
47      * @Description: 用户验证的核心流程(模版方法),定义算法的骨架
48      * @param domain
49      * @param userInfo
50      * @return 
51      * Map<String,Object> 返回类型
52      */
53     public final Object userCheck(Object...obj) {
54         //1、验证第三方域名是否可用
55         domainCheck(obj);
56         //2、向第三方发起http请求,验证用户帐号信息是否合法
57         Object response = tryCheckUser(obj);
58         //3、将第三方响应的信息包装成我们系统的标准信息
59         return wrapperResult(response);
60     }
61 }
 1 package cn.com.pep.model.template;
 2 /**
 3  * @ClassName: UserCheck4ClientA
 4  * @Description: 渠道A用户的认证实现
 5  * @author: wwh
 6  * @date: 2023年2月24日 下午2:17:44
 7  */
 8 public class UserCheck4ClientA extends AbstractUserCheck {
 9 
10     /**
11      * @Title: wrapperResult
12      * @Description: 包装渠道B的认证结果
13      * @param obj
14      * @return
15      * @see cn.com.pep.model.template.AbstractUserCheck#wrapperResult(java.lang.Object[])
16      */
17     @Override
18     protected Object wrapperResult(Object...obj) {
19         //伪代码
20         System.err.println("包装渠道到A的返回的认证结果...");
21         return "OK";
22     }
23 
24     /**
25      * @Title: tryCheckUser
26      * @Description: 渠道A的具体的用户认证逻辑
27      * @param obj
28      * @return
29      * @see cn.com.pep.model.template.AbstractUserCheck#tryCheckUser(java.lang.Object[])
30      */
31     @Override
32     protected Object tryCheckUser(Object ...obj) {
33         //伪代码
34         System.err.println("****【组织渠道A的请求参数...】****");
35         System.err.println("****【向渠道A开发的域名接口发起用户认证...】");
36         System.err.println("****【将渠道A的认证结果返回...】****");
37         return "OK";
38     }
39 }

 1 package cn.com.pep.model.template;
 2 /**
 3  * @ClassName: UserCheck4ClientB
 4  * @Description: 渠道B的用户认证实现
 5  * @author: wwh
 6  * @date: 2023年2月24日 下午2:20:09
 7  */
 8 public class UserCheck4ClientB extends AbstractUserCheck{
 9     
10     /**
11      * @Title: wrapperResult
12      * @Description: 包装渠道B的认证结果
13      * @param obj
14      * @return
15      * @see cn.com.pep.model.template.AbstractUserCheck#wrapperResult(java.lang.Object[])
16      */
17     @Override
18     protected Object wrapperResult(Object ...obj) {
19         //伪代码
20         System.err.println("包装渠道到B的返回的认证结果...");
21         return "OK";
22     }
23 
24     /**
25      * @Title: tryCheckUser
26      * @Description: 渠道B的用户的具体的认证逻辑
27      * @param obj
28      * @return
29      * @see cn.com.pep.model.template.AbstractUserCheck#tryCheckUser(java.lang.Object[])
30      */
31     @Override
32     protected Object tryCheckUser(Object ...obj) {
33         //伪代码
34         System.err.println("****【组织渠道B的请求参数...】****");
35         System.err.println("****【向渠道B开发的域名接口发起用户认证...】****");
36         System.err.println("****【将渠道B的认证结果返回...】****");
37         return "OK";
38     }
39 }
 1 package cn.com.pep.model.template;
 2 /**
 3  * 
 4  * @ClassName: Client
 5  * @Description: 调用类
 6  * @author: wwh
 7  * @date: 2023年2月24日 下午2:49:34
 8  */
 9 public class Client {
10     
11     public static void main(String[] args) {
12         //1、登录端登录,传入渠道标识
13         AbstractUserCheck userCheck = null;
14         if ("渠道A") {
15             userCheck = new UserCheck4ClientA();
16         }else if ("渠道B") {
17             userCheck = new UserCheck4ClientB();
18         }
19         userCheck.userCheck("用户输入的信息...");
20         System.err.println("用户验证成功...");
21     }
22 }

  模版方法的优点:

  1、把一个算法中认为是不可变的方法封装到父类中实现,并禁止继承,而可变的部分可以通过子类继承来进行重写,简单来讲就是“封装不可变、扩展可变”,符合程序设计的开闭原则;

  2、提取公共部分代码,便于后期维护,基本方法是由子类来实现的,因此可以通过扩展子类的方式来增加响应的功能;

  模版方法的缺点:

1、子类的执行结果影响了父类,可能会造成阅读上的难度;

·   2、每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大;

  注意事项:

为了防止模版方法被恶意重写,一般模版方法加上final修饰。

  JDK源码中模版方法的应用:

  1、上面提到的AQS中的模版方法acquire(int)、acquireShared(int)、release(int)、releaseShared(int)等;

  2、JDK1.8中Map接口中的putIfAbsent(K key, V value)方法就是一个模版方法,其中调用了get(K key)和put(K key,V value)基本操作方法,但是这两个方法在Map接口中并没有实现,而是交由它的实现类去实现。

  3、JDK1.8中HashMap中的putVal()方法就是一个模版方法,而afterNodeAccess()、afterNodeInsertion()、afterNodeRemoval()都是基本操作方法,需要具体的子类去按需实现。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK