模板模式

mac2025-05-01  5

 

一、什么是模板

        既然是模板设计思维,那首先要探索一下到底何为模板,搞清楚现实中模板是啥样的,才能进一步把模板抽象化。

        一提到模板,我最先想到的是PowerPoint,没错,就是让人头疼的PPT,马上要述职汇报了,PPT确实让人头大~

人事的小姐姐已经把汇报的ppt模板发给我们了,我看了下汇报流程,如下:

不管你是张三、李四还是王五,都要按照这4个方面按顺序进行汇报,不能别出心裁搞个性化,这个就是模板的作用。 

百度百科给了“模板”一个定义:“模板是将一个事物的结构规律予以固定化、标准化的成果,它体现的是结构形式的标准化。”

 

二、现实生活中的模板

勤劳的小伙伴都做过饭吧?一起炒个菜吧!

 

 

以上,炒茄子和炒藕带都可以大致分成四步,其中第一和第四步都是一样的,第二步因为炒的菜不同,第三步根据口味放的调料不同,但无论是炒啥菜都基本遵循以上四步,只是个别步骤内容不同,这样我们就可以把上面的步骤抽象成模板,如下:

 

三、模板的代码实现

上面的模板是现实世界的抽象出来的,如何在代码的世界实现呢?

一般需要一个基类及其子类进行实现,基类一般是一个抽象类,为所有子类提供算法框架;子类则根据自身需求提供具体的实现。上面的可以炒XXX的模板可以理解为抽象基类,炒茄子或炒藕带就是具体子类。

代码实现如下:

抽象基类

/** * 抽象基类,为所有子类提供算法框架 * (为什么是抽象类而不是接口? 因为抽象类内允许实现一些共有或通用的方法,这些方法就 * 无需每个子类再去实现了,如上面的”准备炒锅“和“装盘”是通用无差别的方法,可以直接在 * 基类实现;而接口内都为抽象方法,不能实现一些通用的方法) */ public abstract class Cooking { /** 炒菜的步骤模板方法 final修饰说明不可被继承修改,模板方法流程是固定的 */ public final void doCooking(){ // 1.准备炒锅 preparePot(); // 2.开始炒菜 startFry(); // 3.放调料 addCondiments(); // 4.装盘 panning(); } /** 1.准备炒锅(该方法为通用无差别方法,直接在基类里实现,private则子类不可见) */ private void preparePot(){ System.out.println("准备炒锅"); } /** * 2.开始炒菜 * (该方法为个性化方法,延迟到子类里实现,定义为抽象方法 * 因此不能用private修饰,用该为protected或default) */ protected abstract void startFry(); /** * 3.放调料 * (该方法为个性化方法,延迟到子类里实现,定义为抽象方法 * 因此不能用private修饰,用该为protected或default) */ protected abstract void addCondiments(); /** 4.装盘(该方法为通用无差别方法,直接在基类里实现,private则子类不可见) */ private void panning(){ System.out.println("装盘"); } }

炒茄子子类

public class Eggplant extends Cooking{ @Override protected void startFry() { System.out.println("开始炒茄子"); } @Override protected void addCondiments() { System.out.println("放盐"); } }

炒藕带子类:

/** * 子类:炒藕带 */ public class Lotus extends Cooking{ @Override protected void startFry() { System.out.println("开始炒藕带"); } @Override protected void addCondiments() { System.out.println("放醋"); } }

测试类:

public class Test { public static void main(String[] args) { Cooking cookEggplant = new Eggplant(); Cooking cookLotus = new Lotus(); cookEggplant.doCooking(); System.out.println("-----------"); cookLotus.doCooking(); } }

结果:

准备炒锅 开始炒茄子 放盐 装盘 ----------- 准备炒锅 开始炒藕带 放醋 装盘

ps:这里延迟到子类里面实现的方法都是用protect修饰的,为什么?

一方面,这种方法只有子类才能实现,其他外部类不能实现,protect修饰可以满足要求;

另一方面,这里不用public是因为模板类里面的方法组成一个整体流程,给外面的类实现其中一个也没有意义。

 

四、钩子方法

以上实现了模板方法,子类根据需要继承并实现基类的抽象方法,但是这里是实现全部的抽象方法,如果现在有个子类不想实现“装盘”方法,炒好了就想直接在锅里吃,怎么办呢?

这时候需要提供一个钩子方法,提供一个默认或者空的实现,此钩子方法可以作为子类是否实现基类某个方法的判断依据。

例如我们在基类添加一个钩子方法如下:

/** * 抽象基类,为为所有子类提供算法框架 * (为什么是抽象类而不是接口? 因为抽象类内允许实现一些共有或通用的方法,这些方法就 * 无需每个子类再去实现了,如上面的”准备炒锅“和“装盘”是通用无差别的方法,可以直接在 * 基类实现;而接口内都为抽象方法,不能实现一些通用的方法) */ public abstract class Cooking { /** 炒菜的步骤模板方法 */ public final void doCooking(){ // 1.准备炒锅 preparePot(); // 2.开始炒菜 startFry(); // 3.放调料 addCondiments(); // 4.装盘 if(willPan()){ panning(); } } /** 1.准备炒锅(该方法为通用无差别方法,直接在基类里实现,private则子类不可见) */ private void preparePot(){ System.out.println("准备炒锅"); } /** * 2.开始炒菜 * (该方法为个性化方法,延迟到子类里实现,定义为抽象方法 * 因此不能用private修饰,用该为protected或default) */ protected abstract void startFry(); /** * 3.放调料 * (该方法为个性化方法,延迟到子类里实现,定义为抽象方法 * 因此不能用private修饰,用该为protected或default) */ protected abstract void addCondiments(); /** 4.装盘(该方法为通用无差别方法,直接在基类里实现,private则子类不可见) */ private void panning(){ System.out.println("装盘"); } /** 钩子方法 (为另一个方法提供默认或空的实现, protected让子类可以覆写)*/ protected boolean willPan(){ return true; } }

子类覆写钩子方法:

/** * 子类:炒茄子 */ public class Eggplant extends Cooking{ @Override protected void startFry() { System.out.println("开始炒茄子"); } @Override protected void addCondiments() { System.out.println("放盐"); } /** 覆写基类的钩子方法 (不“装盘”)*/ @Override protected boolean willPan(){ return false; } }

测试类Test:

public class Test { public static void main(String[] args) { Cooking cookEggplant = new Eggplant(); Cooking cookLotus = new Lotus(); cookEggplant.doCooking(); System.out.println("-----------"); cookLotus.doCooking(); } }

结果:

准备炒锅 开始炒茄子 放盐 ----------- 准备炒锅 开始炒藕带 放醋 装盘

炒茄子已经不装盘了。可以看出,钩子方法使子类自由度提高了,想不实现哪个流程就不用实现,但总体还是按照模板进行的

 

五、优缺点

以上过程以不难看出模板设计模式的优点

优点:

封装性好复用性好屏蔽细节便于维护

缺点:

由于java的单继承性,一旦一个类采用了模板设计模式,就意味着它需要继承基类,也同时意味着它不能再继承另一个类。

 每个不同的实现都需要定义一个子类,会导致类的个数增加。

 

六、适用的场景

算法或操作遵循相似的逻辑或流程把相同的代码抽取到父类中时当算法很复杂时,可以利用模板方法将算法进行分解

 

七、总结

模板设计的现实需要两个要素:抽象基类和子类

抽象基类中,将部分通用无差别的逻辑以具体方法实现;另外个性化差异的部分声明成抽象方法,在其子类中延迟实现;钩子方法增加子类的灵活性;最后将所有方法汇总成一个finnal修饰的模板方法,调用该方法即可完成整个流程。

 

 

 

最新回复(0)