模板方法(Template Method)---行为型
1 基础知识
定义:定义了一个算法的骨架并允许子类为一个或多个步骤提供实现。特征:模板方法使得子类可以在不改变算法结构的前提下重新定义某些步骤。
使用场景:
(1)需要固定定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况。
(2)n各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复
(3)需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展。
优点:提高复用性、扩展性;符合开闭原则。缺点:类数目增加;增加了系统实现的复杂度;模板方法主要通过继承实现,继承本身的缺点:如果父类添加新的抽象方法,所有子类都要实现一遍。
2 代码示例
使用场景:假设一个企业级应用系统,需要实现两种控制:普通用户登录和工作人员登录。在判断登录方法中有许多类似功能的代码也有许多特有的方法。
普通用户:User
public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
普通用户登录:UserLogin
public class UserLogin { User user = new User(); public void login(){ //前台设置 user.setName("普通用户"); System.out.println(user.getName()); } }
工作人员:Worker
public class Worker { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
工作人员登录:WorkLogin
public class WorkLogin { Worker worker = new Worker(); public void login(){ //设置名字这些其实应该是是前台设置,没有前台因此在此处设置了 worker.setName("工作人员"); //为了简化逻辑在这里是只输出一个名字 System.out.println(worker.getName()); } }
在这里只是简单的判断了一下登录方法,但在实际问题中登录方法也行很复杂,涉及到很多模块的共同完成,有些模块可能是两者都需要的,这样就会造成大量的重复代码,因此这样的进行设计肯定会造成代码的臃肿。故采用模板方法,对其中公共的代码进行抽取。
User和Worker实体类还是保持不变,在这里以User为例:
public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
登录模板类:LoginTemplate 要采用抽象类因为对于公共的方法要进行具体实现
public abstract class LoginTemplate { //两类特有的判断登录的方法 protected abstract void login(); //对于两者都有的公共方法可以写在这里 public void common(){ System.out.println("这是公共的方法"); } }
普通员工登录类:UserLogin 工作人员登录类与其类似因此在这里只以普通员工为例
public class UserLogin extends LoginTemplate{ User user = new User(); @Override protected void login() { user.setName("普通用户"); System.out.println(user.getName()); } }
在应用层:Test
public class Test { public static void main(String[] args) { LoginTemplate userLogin = new UserLogin(); //可以调用公共的方法 userLogin.common(); //调用特有的判断登录方法 userLogin.login(); } }
下面的例子是视频中所讲的,感觉理解起来没有第一个例子好。在教育网站制作课程时,对于PPT等一些素材是必备的而一些如手记则可能有的课程有些没有。
抽象的课程类:
public abstract class ACourse { protected final void makeCourse(){ this.makePPT(); this.makeVideo(); //是否写手记交由钩子方法实现 if(needWriteArticle()){ this.writeArticle(); } this.packageCourse(); } //final方法子类不可以进行覆盖 final void makePPT(){ System.out.println("制作PPT"); } final void makeVideo(){ System.out.println("制作视频"); } final void writeArticle(){ System.out.println("编写手记"); } //钩子方法 protected boolean needWriteArticle(){ return false; } //包装方法 abstract void packageCourse(); }
具体课程类:DesignPatternCourse
public class DesignPatternCourse extends ACourse { @Override void packageCourse() { System.out.println("提供课程Java源代码"); } @Override //重写父类方法,有手记 protected boolean needWriteArticle() { return true; } }
具体课程类:
public class FECourse extends ACourse { @Override void packageCourse() { System.out.println("提供课程的前端代码"); System.out.println("提供课程的图片等多媒体素材"); } @Override protected boolean needWriteArticle() { return this.needWriteArticleFlag; } }
应用层:
public class Test { public static void main(String[] args) { System.out.println("后端设计模式课程start---"); ACourse designPatternCourse = new DesignPatternCourse(); designPatternCourse.makeCourse(); System.out.println("后端设计模式课程end---"); System.out.println("前端课程start---"); ACourse feCourse = new FECourse(); feCourse.makeCourse(); System.out.println("前端课程end---"); } }
这样就显示后端课程有手记,前端课程没有手记。那么再复杂化一下:前端课程中有些课程没有,有些有手记,如果还按照上面的方法那么就会造成前端课程中都有或者没有了。对前端课程进行修改:
public class FECourse extends ACourse { //声明一个变量通过构造器方式开放给应用层。 private boolean needWriteArticleFlag = false; @Override void packageCourse() { System.out.println("提供课程的前端代码"); System.out.println("提供课程的图片等多媒体素材"); } public FECourse(boolean needWriteArticleFlag) { this.needWriteArticleFlag = needWriteArticleFlag; } @Override protected boolean needWriteArticle() { return this.needWriteArticleFlag; } }
在应用层:这样把权限开放给了应用层。
public class Test { public static void main(String[] args) { System.out.println("前端课程start---"); ACourse feCourse1 = new FECourse(true); feCourse.makeCourse(); System.out.println("前端课程end---"); System.out.println("前端课程start---"); ACourse feCourse2 = new FECourse(false); feCourse.makeCourse(); System.out.println("前端课程end---"); } }
其类关系图如下:
3 思考模板方法模式
(1)模板方法模式的本质
模板方法模式的本质:固定算法骨架。
模板方法模式主要是通过制定模板,把算法步骤固定下来,至于谁来实现,模板可以自己提供实现,也可以由子类去实现,还可以通过回调机制让其他类来实现。通过固定算法骨架来约東子类的行为,并在特定的扩展点来让子类进行功能扩展,从而让程序既有很好的复用性,又有较好的扩展性。
(2)对设计原则的体现
模板方法很好地体现了开闭原则和里氏替换原则。
首先从设计上分离变与不变,然后把不变的部分抽取出来,定义到父类中,比如算法骨架,一些公共的、固定的实现等。这些不变的部分被封闭起来,尽量不去修改它们。要想扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的。其次,能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板,并能在使用模板的地方,根据需要切换不同的具体实现。
4 相关模式
(1)模板方法模式和工厂方法模式
这两个模式可以配合使用。模板方法模式可以通过工厂方法来获取需要调用的对象。
(2)模板方法模式和策略模式
这两个模式的功能有些相似,但是是有区别的。从表面上看,两个模式都能实现算法的封装,但是模板方法封装的是算法的骨架,这个算法骨架是不变的,变化的是算法中某些步骤的具体实现:而策略模式是把某个步骤的具体实现算法封装起来,所有封装的算法对象是等价的,可以相互替换。因此,可以在模板方法中使用策略模式,就是把那些变化的算法步骤通过使用策略模式来实现,但是具体选取哪个策略还是要由外部来确定,而整体的算法步骤,也就是算法骨架则由模板方法来定义了。
0