找回密码
 立即注册
首页 业界区 业界 设计模式之(13)--模板方法模式

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

溜椎干 昨天 09:01
  今天我们来学习下模板方法设计模式。
  模板方法(Template Method Pattern):抽象的父类中定义一个操作中算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤。简单地讲,就是“父类的模板方法定义不变的流程,子类重写流程中的方法”。
  类图如下所示:
  
1.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. 1 package cn.com.pep.model.template;
  2. 2 /**
  3. 3  * @ClassName: AbstractSuperClass
  4. 4  * @Description: 验证用户的模版类
  5. 5  * @author: wwh
  6. 6  * @date: 2023年2月24日 下午1:45:06
  7. 7  */
  8. 8 public abstract class AbstractUserCheck {
  9. 9     
  10. 10     /**
  11. 11      * @Title: domainCheck
  12. 12      * @Description: 检测第三方提供的域名是否可用
  13. 13      * @param domain
  14. 14      * @return
  15. 15      * String 返回类型
  16. 16      */
  17. 17     private String domainCheck(Object domain){
  18. 18         //伪代码
  19. 19         System.err.println("域名可访问...");
  20. 20         return "ok";
  21. 21     };
  22. 22     
  23. 23     /**
  24. 24      * @Title: tryCheckUser
  25. 25      * @Description: 不同渠道用户验证的逻辑,强制子类趋势线
  26. 26      * @param obj
  27. 27      * @return Object 返回类型
  28. 28      */
  29. 29     protected Object tryCheckUser(Object ...obj) {
  30. 30         throw new UnsupportedOperationException();
  31. 31     }
  32. 32     
  33. 33     /**
  34. 34      * @Title: wrapperResult
  35. 35      * @Description: 对各个渠道的验证结果进行转换,包装为系统的响应结果
  36. 36      * @param response
  37. 37      * @return
  38. 38      * Map<String,Object> 返回类型
  39. 39      */
  40. 40     protected Object wrapperResult(Object ...obj) {
  41. 41         throw new UnsupportedOperationException();
  42. 42     }
  43. 43     
  44. 44     /**
  45. 45      *
  46. 46      * @Title: userCheck
  47. 47      * @Description: 用户验证的核心流程(模版方法),定义算法的骨架
  48. 48      * @param domain
  49. 49      * @param userInfo
  50. 50      * @return
  51. 51      * Map<String,Object> 返回类型
  52. 52      */
  53. 53     public final Object userCheck(Object...obj) {
  54. 54         //1、验证第三方域名是否可用
  55. 55         domainCheck(obj);
  56. 56         //2、向第三方发起http请求,验证用户帐号信息是否合法
  57. 57         Object response = tryCheckUser(obj);
  58. 58         //3、将第三方响应的信息包装成我们系统的标准信息
  59. 59         return wrapperResult(response);
  60. 60     }
  61. 61 }
复制代码
 
  1. 1 package cn.com.pep.model.template;
  2. 2 /**
  3. 3  * @ClassName: UserCheck4ClientA
  4. 4  * @Description: 渠道A用户的认证实现
  5. 5  * @author: wwh
  6. 6  * @date: 2023年2月24日 下午2:17:44
  7. 7  */
  8. 8 public class UserCheck4ClientA extends AbstractUserCheck {
  9. 9
  10. 10     /**
  11. 11      * @Title: wrapperResult
  12. 12      * @Description: 包装渠道B的认证结果
  13. 13      * @param obj
  14. 14      * @return
  15. 15      * @see cn.com.pep.model.template.AbstractUserCheck#wrapperResult(java.lang.Object[])
  16. 16      */
  17. 17     @Override
  18. 18     protected Object wrapperResult(Object...obj) {
  19. 19         //伪代码
  20. 20         System.err.println("包装渠道到A的返回的认证结果...");
  21. 21         return "OK";
  22. 22     }
  23. 23
  24. 24     /**
  25. 25      * @Title: tryCheckUser
  26. 26      * @Description: 渠道A的具体的用户认证逻辑
  27. 27      * @param obj
  28. 28      * @return
  29. 29      * @see cn.com.pep.model.template.AbstractUserCheck#tryCheckUser(java.lang.Object[])
  30. 30      */
  31. 31     @Override
  32. 32     protected Object tryCheckUser(Object ...obj) {
  33. 33         //伪代码
  34. 34         System.err.println("****【组织渠道A的请求参数...】****");
  35. 35         System.err.println("****【向渠道A开发的域名接口发起用户认证...】");
  36. 36         System.err.println("****【将渠道A的认证结果返回...】****");
  37. 37         return "OK";
  38. 38     }
  39. 39 }<br><br>
复制代码
  1. 1 package cn.com.pep.model.template;
  2. 2 /**
  3. 3  * @ClassName: UserCheck4ClientB
  4. 4  * @Description: 渠道B的用户认证实现
  5. 5  * @author: wwh
  6. 6  * @date: 2023年2月24日 下午2:20:09
  7. 7  */
  8. 8 public class UserCheck4ClientB extends AbstractUserCheck{
  9. 9     
  10. 10     /**
  11. 11      * @Title: wrapperResult
  12. 12      * @Description: 包装渠道B的认证结果
  13. 13      * @param obj
  14. 14      * @return
  15. 15      * @see cn.com.pep.model.template.AbstractUserCheck#wrapperResult(java.lang.Object[])
  16. 16      */
  17. 17     @Override
  18. 18     protected Object wrapperResult(Object ...obj) {
  19. 19         //伪代码
  20. 20         System.err.println("包装渠道到B的返回的认证结果...");
  21. 21         return "OK";
  22. 22     }
  23. 23
  24. 24     /**
  25. 25      * @Title: tryCheckUser
  26. 26      * @Description: 渠道B的用户的具体的认证逻辑
  27. 27      * @param obj
  28. 28      * @return
  29. 29      * @see cn.com.pep.model.template.AbstractUserCheck#tryCheckUser(java.lang.Object[])
  30. 30      */
  31. 31     @Override
  32. 32     protected Object tryCheckUser(Object ...obj) {
  33. 33         //伪代码
  34. 34         System.err.println("****【组织渠道B的请求参数...】****");
  35. 35         System.err.println("****【向渠道B开发的域名接口发起用户认证...】****");
  36. 36         System.err.println("****【将渠道B的认证结果返回...】****");
  37. 37         return "OK";
  38. 38     }
  39. 39 }
复制代码
 
  1. 1 package cn.com.pep.model.template;
  2. 2 /**
  3. 3  *
  4. 4  * @ClassName: Client
  5. 5  * @Description: 调用类
  6. 6  * @author: wwh
  7. 7  * @date: 2023年2月24日 下午2:49:34
  8. 8  */
  9. 9 public class Client {
  10. 10     
  11. 11     public static void main(String[] args) {
  12. 12         //1、登录端登录,传入渠道标识
  13. 13         AbstractUserCheck userCheck = null;
  14. 14         if ("渠道A") {
  15. 15             userCheck = new UserCheck4ClientA();
  16. 16         }else if ("渠道B") {
  17. 17             userCheck = new UserCheck4ClientB();
  18. 18         }
  19. 19         userCheck.userCheck("用户输入的信息...");
  20. 20         System.err.println("用户验证成功...");
  21. 21     }
  22. 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()都是基本操作方法,需要具体的子类去按需实现。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册