【引入】
考试我们早已经习以为常了,有没有思考过,考试试卷是机子印刷的好处是什么?当然,首先比手写快是肯定的;但是,关键在于不会出错,因为大家的题目都应该是一模一样的。这就是一个进店的设计模式——模板方法模式。
模板方法模式抽象出某个业务操作公共的流程,将流程分为几个步骤,其中有一些步骤是固定不变的,有一些步骤是变化的,固定不变的步骤通过一个基类来实现,而变化的部分通过钩子方法让子类去实现,这样就实现了对系统中流程的统一化规范化管理。
一、模板方法模式
模板方法模式(TemplateMethod)定义一个操作中的算法的框架,而将这些步骤的实现延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
其中它的方法分为两类:1、基本方法。2、模板方法(模板方法是对基本方法的调用,有一定的逻辑,一般加上final关键词)。
UML类图
【代码实现】
AbstractClass
public abstract class AbstractClass {
public abstract void operation1();
public abstract void operation2();
public void TemplateMethod() {
operation1();
operation2();
System.out.println("");
}
}
ConcreteClassA类
public class ConcreteClassA extends AbstractClass {
@Override
public void operation1() {
System.out.println("具体类A方法1实现");
}
@Override
public void operation2() {
System.out.println("具体类A方法2实现");
}
}
ConcreteClassB类
public class ConcreteClassB extends AbstractClass{
@Override
public void operation1() {
System.out.println("具体类B方法1实现");
}
@Override
public void operation2() {
System.out.println("具体类B方法2实现");
}
}
二、场景实现
实例:化学考试试卷
TestPaper类(AbstractClass类)
/**
* 化学试卷考题
*/
public class TestPaper {
public void topicOne() {
System.out.println("1、下列常用实验仪器中,不能直接用于混合物的分离或提纯的是() \n" +
"A.分液漏斗 B.普通漏斗 C.蒸馏烧瓶 D.容量瓶 ");//D
}
public void topicTwo() {
System.out.println("2、下列解释正确的是 ( ) \n" +
"A.物质的量就是物质的质量 B.物质的量是一个独立的专有名词 C.摩尔是物质的质量的单位 D.摩尔质量等于相对分子质量");//B
}
public void topicThree() {
System.out.println("3、下列实验仪器不宜直接用来加热的是 ( )\n" +
"A.试管 B.坩埚 C.蒸发皿 D.烧杯");//D
}
}
A同学答卷
public class TestPaperA extends TestPaper{
@Override
public void topicOne() {
super.topicOne();
System.out.println("答案:D");
}
@Override
public void topicTwo() {
super.topicTwo();
System.out.println("答案:B");
}
@Override
public void topicThree() {
super.topicThree();
System.out.println("答案:D");
}
}
B同学答卷
public class TestPaperB extends TestPaper{
@Override
public void topicOne() {
super.topicOne();
System.out.println("答案:D");
}
@Override
public void topicTwo() {
super.topicTwo();
System.out.println("答案:B");
}
@Override
public void topicThree() {
super.topicThree();
System.out.println("答案:D");
}
}
客户端
public class Client {
public static void main(String[] args) {
System.out.println("-------- A同学抄写的试卷 -------");
TestPaperA A=new TestPaperA();
A.topicOne();
A.topicTwo();
A.topicThree();
System.out.println("-------- B同学抄写的试卷 -------");
TestPaperB B=new TestPaperB();
B.topicOne();
B.topicTwo();
B.topicThree();
}
}
三、模板方法模式应用场景以及优缺点
1、优点
- 模板方法模式通过把不变的行为搬移到父类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的扩展。
- 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
2、缺点
- 按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事务属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度。
3、应用场景
- 多个子类有共有的方法,并且逻辑基本相同。
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
- 重构时,模板方法是一个经常使用的方法,把相同的代码抽取到父类中,然后通过构造函数约束其行为。
四、应用实例
1、JDK中模板方法的体现
JUC中AQS本身是提供了一套同步控制器的模板,可以通过这一套模板,实现ReentrantLock
、ReentrantReadWriteLock
、CountDownLatch
、Semaphore
等。
如果需要自定义同步器,一般方法是:继承AQS,并重写指定方法(无非是按照自己定义的规则对state的获取与释放);将AQS组合在自定义同步组件的实现中,并调用模板方法,而这些模板方法会调用重写的方法。
需要重写的方法:
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
以上方法默认抛出UnsupportedOperationException异常,AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
b、集合中也大量用到模板方法
AbstractList、AbstractCollection等都是很多集合类的父类,所以会把一些公共部分放在抽象父类中,一些可扩展的方法让子类实现。拿AbstractList举例,AbstractList本身也是AbstractCollection的子类:
2、tomcat的生命历程
Tomcat中关于生命周期管理的地方很好应用了模板方法模式,在一个组件的生命周期中都会涉及到init(初始化),start(启动),stop(停止),destory(销毁),而对于每一个生命周期阶段其实都有固定一些事情要做,比如判断前置状态,设置后置状态,以及通知状态变更事件的监听者等,而这些工作其实是可以固化的,所以Tomcat中就将每个生命周期阶段公共的部分固化,然后通过initInternal、startInternal、stopInternal、destoryInternal这几个钩子方法(在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况覆盖它,该方法称为钩子方法。)开放给子类去实现具体的逻辑。
说明:
- tomcat的所有容器都实现了Lifecycle的生命周期管理接口,这里以Tomcat几个核心组件为例。
- 抽象基类LifecycleBase实现Lifecycle接口,在基类当中实现了模板接口start、stop、init等方法接口。