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

在上面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()都是基本操作方法,需要具体的子类去按需实现。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这