当前位置:首页>编程知识库>后端开发知识>设计模式是什么鬼(模板方法)
设计模式是什么鬼(模板方法)
阅读 1
2018-08-21
点击下方阅读原文,查看更多关于设计模式的文章
面向对象,是对事物属性与行为的封装,方法,指的就是行为。模板方法,显而易见是说某个方法充当了模板的作用,其充分利用了抽象类虚实结合的特性,虚部抽象预留,实部固定延续,以达到将某种固有行为延续至子类的目的。反观接口,则达不到这种目的。要搞明白模板方法,首先我们从接口与抽象类的区别切入,这也是面试官经常会问到的问题。
汽车上的接口最常见的就是这几个了,点烟器,USBAUX等等,很明显这些都是接口,它们都预留了某种标准,暴露在系统外部,并与外设对接。就拿点烟器接口来说吧,它原本是专门用于给点烟器供电的,后来由于这个接口在汽车上的通用性,于是衍生出了各种外部设备,只要是符合这个标准size的,带正负极簧片的,直流12V的,那就可以使用,比如导航、行车记录仪、吸尘器什么的,以及其他各种车载电子设备。
public%20interface%20CigarLighterInterface%20%7B//%E7%82%B9%E7%83%9F%E5%99%A8%E6%8E%A5%E5%8F%A3%0A%20%20%20%20//%E4%BE%9B%E7%94%B5%E6%96%B9%E6%B3%95%EF%BC%8C16V%E7%9B%B4%E6%B5%81%E7%94%B5%0A%20%20%20%20public%20void%20electrifyDC16V();%0A%7D%0A
1public%20class%20GPS%20implements%20CigarLighterInterface%20%7B%0A2%20%20%20%20//%E5%AF%BC%E8%88%AA%E7%9A%84%E5%AE%9E%E7%8E%B0%0A3%20%20%20%20@Override%0A4%20%20%20%20public%20void%20electrifyDC16V()%20%7B%0A5%20%20%20%20%20%20%20%20System.out.println(%22%E8%BF%9E%E6%8E%A5%E5%8D%AB%E6%98%9F%22);%0A6%20%20%20%20%20%20%20%20System.out.println(%22%E5%AE%9A%E4%BD%8D%E3%80%82%E3%80%82%E3%80%82%22);%0A7%20%20%20%20%7D%0A8%0A9%7D%0A
%201public%20class%20CigarLighter%20implements%20CigarLighterInterface%20%7B%0A%202%20%20%20%20//%E7%82%B9%E7%83%9F%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%0A%203%20%20%20%20@Override%0A%204%20%20%20%20public%20void%20electrifyDC16V()%20%7B%0A%205%20%20%20%20%20%20%20%20int%20time%20=%201000;%0A%206%20%20%20%20%20%20%20%20while(--time>0)%7B%0A%207%20%20%20%20%20%20%20%20%20%20%20%20System.out.println(%22%E5%8A%A0%E7%83%AD%E7%94%B5%E7%82%89%E4%B8%9D%22);%0A%208%20%20%20%20%20%20%20%20%7D%0A%209%20%20%20%20%20%20%20%20System.out.println(%22%E7%82%B9%E7%83%9F%E5%99%A8%E5%BC%B9%E5%87%BA%22);%0A10%20%20%20%20%7D%0A11%0A12%7D%0A
对于点烟器接口来说,它根本不在乎也不知道对接的外设是什么鬼,它只是定义了一种规范,一种标准,只要符合的都可以对接。再比如USB接口的应用更加广泛,外设更是应有尽有,具体例子可以参考文章《设计模式是什么鬼(初探)》。
以上我们可以体会到接口的抽象是淋漓尽致的,实现是空无的,也就是说其方法都是无实现的。然而这样在某些场景下会存在一些问题,例如有时候我们在父类中只需抽象出一些方法,并且同时也有一些实体方法,以供子类直接继承,这怎么办?答案就是抽象类。举个例子,哺乳动物类,我们人类就是哺乳动物。
什么?鲸鱼是哺乳类?是的,凡是喂奶的都是哺乳类,不要以为会游泳的都是鱼,会飞的都是鸟,蝙蝠同样是哺乳类,只不过是老鼠中的飞行员而已。
既然如此这哺乳动物肯定是都能喂奶了,但是到底是跑还是游,或是飞呢还真不好说,但至少可以确认它们都是可以移动的。言归正传,我们开始定义哺乳动物抽象类。
%201public%20abstract%20class%20Mammal%20%7B%0A%202%0A%203%20%20%20%20//%E6%97%A2%E7%84%B6%E6%98%AF%E5%93%BA%E4%B9%B3%E5%8A%A8%E7%89%A9%E5%BD%93%E7%84%B6%E4%BC%9A%E5%96%82%E5%A5%B6%E4%BA%86%EF%BC%8C%E4%BD%86%E8%BF%99%E9%87%8C%E7%BA%A6%E6%9D%9F%E4%B8%BA%E5%8F%AA%E8%83%BD%E6%AF%8D%E7%9A%84%E5%96%82%E5%A5%B6%0A%204%20%20%20%20protected%20final%20void%20feedMilk()%7B%0A%205%20%20%20%20%20%20%20%20if(female)%7B//%E5%A6%82%E6%9E%9C%E6%98%AF%E6%AF%8D%E7%9A%84%E2%80%A6%E2%80%A6%0A%206%20%20%20%20%20%20%20%20%20%20%20%20System.out.println(%22%E5%96%82%E5%A5%B6%22);%0A%207%20%20%20%20%20%20%20%20%7Delse%7B//%E5%A6%82%E6%9E%9C%E6%98%AF%E5%85%AC%E7%9A%84%E2%80%A6%E2%80%A6%E6%88%96%E8%80%85%E5%8F%AF%E4%BB%A5%E6%8A%9B%E4%B8%AA%E5%BC%82%E5%B8%B8%E5%87%BA%E5%8E%BB%E3%80%82%0A%208%20%20%20%20%20%20%20%20%20%20%20%20System.out.println(%22%E5%85%AC%E7%9A%84%E4%B8%8D%E4%BC%9A%22);%0A%209%20%20%20%20%20%20%20%20%7D%0A10%20%20%20%20%7D%0A11%0A12%20%20%20%20//%E5%93%BA%E4%B9%B3%E5%8A%A8%E7%89%A9%E5%BD%93%E7%84%B6%E5%8F%AF%E4%BB%A5%E7%A7%BB%E5%8A%A8%EF%BC%8C%E4%BD%86%E5%85%B7%E4%BD%93%E6%80%8E%E4%B9%88%E7%A7%BB%E5%8A%A8%E8%BF%98%E4%B8%8D%E7%9F%A5%E9%81%93%E3%80%82%0A13%20%20%20%20public%20abstract%20void%20move();%0A14%7D%0A
这里我们省略了female属性,其作用是为了控制喂奶行为,大家可以自行添加。可以看到的是,抽象类不同于接口,其自身是可以有具体实现的,也就是说抽象类是虚实结合的,虚部抽象行为,实部遗传给子类,虚虚实实,飘忽不定。OK,我们看下人、鲸、蝠的子类实现。
public%20class%20Human%20extends%20Mammal%20%7B%0A%0A%20%20%20%20@Override%0A%20%20%20%20public%20void%20move()%20%7B%0A%20%20%20%20%20%20%20%20System.out.println(%22%E4%B8%A4%E6%9D%A1%E8%85%BF%E8%B5%B0%E8%B7%AF%E2%80%A6%E2%80%A6%22);%0A%20%20%20%20%7D%0A%0A%7D%0A
public%20class%20Whale%20extends%20Mammal%20%7B%0A%0A%20%20%20%20@Override%0A%20%20%20%20public%20void%20move()%20%7B%0A%20%20%20%20%20%20%20%20System.out.println(%22%E6%B8%B8%E6%B3%B3%E2%80%A6%E2%80%A6%22);%0A%20%20%20%20%7D%0A%0A%7D%0A
public%20class%20Bat%20extends%20Mammal%20%7B%0A%0A%20%20%20%20@Override%0A%20%20%20%20public%20void%20move()%20%7B%0A%20%20%20%20%20%20%20%20System.out.println(%22%E7%94%A8%E7%BF%85%E8%86%80%E9%A3%9E%E2%80%A6%E2%80%A6%22);%0A%20%20%20%20%7D%0A%0A%7D%0A
可以看到子类的各路实现都是自己独有的行为方式,而喂奶那个行为是不需要自己实现的,它是属于抽象哺乳类的共有行为,哺乳子类不能进行任何干涉。这便是接口与抽象的最大区别了,接口是虚的,抽象类可以有虚有实,接口不在乎实现类是什么,抽象类会延续其基因给子类。
其实到这里我们已经说了一大半了,理解了以上部分,剩下的部分就非常简单了,利用抽象类的这个特性,便有了“模板方法”。举例说明,我们做软件项目管理,按瀑布式简单来讲分为:需求分析、设计、编码、测试、发布,先不管是用何种方式去实现各个细节,我们就抽象成这五个步骤。
public%20abstract%20class%20PM%20%7B%0A%20%20%20%20protected%20abstract%20void%20analyze();//%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%0A%20%20%20%20protected%20abstract%20void%20design();//%E8%AE%BE%E8%AE%A1%0A%20%20%20%20protected%20abstract%20void%20develop();//%E5%BC%80%E5%8F%91%0A%20%20%20%20protected%20abstract%20boolean%20test();//%E6%B5%8B%E8%AF%95%0A%20%20%20%20protected%20abstract%20void%20release();//%E5%8F%91%E5%B8%83%0A%7D%0A
那么问题来了,有个程序员在需求不明确或者设计不完善的情况下,一上来二话不说直接写代码,这样就会造成资源的浪费,后期改动太大还会影响项目进度。那么项目经理这时就应该规范一下这个任务流程,这里我们加入kickoff()方法实现。
%201public%20abstract%20class%20PM%20%7B%0A%202%20%20%20%20protected%20abstract%20void%20analyze();//%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%0A%203%20%20%20%20protected%20abstract%20void%20design();//%E8%AE%BE%E8%AE%A1%0A%204%20%20%20%20protected%20abstract%20void%20develop();//%E5%BC%80%E5%8F%91%0A%205%20%20%20%20protected%20abstract%20boolean%20test();//%E6%B5%8B%E8%AF%95%0A%206%20%20%20%20protected%20abstract%20void%20release();//%E5%8F%91%E5%B8%83%0A%207%0A%208%20%20%20%20protected%20final%20void%20kickoff()%7B%0A%209%20%20%20%20%20%20%20%20analyze();%0A10%20%20%20%20%20%20%20%20design();%0A11%20%20%20%20%20%20%20%20develop();%0A12%20%20%20%20%20%20%20%20test();%0A13%20%20%20%20%20%20%20%20release();%0A14%20%20%20%20%7D%0A15%7D%0A
这样就限制了整个项目周期的任务流程,注意这里要用final声明此方法子类不可以重写,只能乖乖的继承下去用。至于其他的抽象方法,子类可以自由发挥,比如测试方法test(),子类可以用人工测试,自动化测试,我们不care,我们是站在项目管理的抽象高度,只把控流程进度。这里甚至我们还可以加入一些逻辑如下。
%201public%20abstract%20class%20PM%20%7B%0A%202%20%20%20%20protected%20abstract%20void%20analyze();//%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%0A%203%20%20%20%20protected%20abstract%20void%20design();//%E8%AE%BE%E8%AE%A1%0A%204%20%20%20%20protected%20abstract%20void%20develop();//%E5%BC%80%E5%8F%91%0A%205%20%20%20%20protected%20abstract%20boolean%20test();//%E6%B5%8B%E8%AF%95%0A%206%20%20%20%20protected%20abstract%20void%20release();//%E5%8F%91%E5%B8%83%0A%207%0A%208%20%20%20%20protected%20final%20void%20kickoff()%7B%0A%209%20%20%20%20%20%20%20%20analyze();%0A10%20%20%20%20%20%20%20%20design();%0A11%20%20%20%20%20%20%20%20do%20%7B%0A12%20%20%20%20%20%20%20%20%20%20%20%20develop();%0A13%20%20%20%20%20%20%20%20%7D%20while%20(!test());//%E5%A6%82%E6%9E%9C%E6%B5%8B%E8%AF%95%E5%A4%B1%E8%B4%A5%EF%BC%8C%E5%88%99%E7%BB%A7%E7%BB%AD%E5%BC%80%E5%8F%91%E6%94%B9Bug%E3%80%82%0A14%20%20%20%20%20%20%20%20release();%0A15%20%20%20%20%7D%0A16%7D%0A
以下子类只需实现抽象方法,而不用实现固有的模板方法kickoff(),因为它已经被父类PM实现了,并且子类也不能进行重写。
1public%20class%20AutoTestPM%20extends%20PM%7B%0A2%0A3%20%20%20%20@Override%0A4%20%20%20%20protected%20void%20analyze()%20%7B%0A5%20%20%20%20%20%20%20%20System.out.println(%22%E8%BF%9B%E8%A1%8C%E4%B8%9A%E5%8A%A1%E6%B2%9F%E9%80%9A%EF%BC%8C%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%22);%20%20%20%20%20%0A6%20%20%20%20%7D%0A7%0A8%20%20%20%20//design();develop();test();release();%E5%AE%9E%E7%8E%B0%E7%9C%81%E7%95%A5%0A9%7D%0A
至此,我们的模板方法就完成了,抽象类PM中的实方法kickoff()中,以某种逻辑编排调用了其他各个抽象方法,提供了一种固定模式的行为方式或是指导方针,以此达到虚实结合、柔中带刚、刚柔并济,灵活中不失规范的目的。
当然大部分情况我们使用接口会多于抽象类,因为接口灵活啊,抽象类不允许多继承啊等等,其实我们还是要看应用场景,在某种无规矩不成方圆,或者规范比较明确,的情况下抽象类的应用是有必要的,世间万物没有最好的,只有最合适的。
看完本文有收获?请转发分享给更多人
以上数据来源于网络,如有侵权,请联系删除。
评论 (0)