二话不说先上代码,如下所示为一个抽象类(抽象汽车模型)与它的两个具体实现类(宝马模型、奔驰模型)的模拟程序:
/*
* 抽象模板类,抽象汽车模型
*/
public abstract class AbstractCarModel {
//汽车要能启动!<span style="white-space:pre"> </span>
protected abstract void start();<span style="white-space:pre">
</span> //汽车要能刹车!
protected abstract void stop();
//汽车要能响喇叭!
protected void alarm(){
//一般汽车喇叭都是“滴滴滴”哦,要想“巴拉拉”或者“啪啪啪”去实现类自己重写去!
System.out.println("滴滴滴滴滴滴");
}
//最重要的是,汽车要能行驶!而且行驶流程是固定的,不能改变!
protected final void run(){
//启动
this.start();
//鸣笛
this.alarm();
//到达目的地就停车
this.stop();
}
}
/*
* 具体实现类,宝马汽车模型
*/
public class BMWModel extends AbstractCarModel{
//实现汽车抽象模板类的start()方法,使宝马能够启动
@Override
protected void start() {
System.out.println("宝马启动!");
}
//实现汽车抽象模板类stop()方法,使宝马能够刹车
@Override
protected void stop() {
System.out.println("宝马停车!");
}
//宝马汽车喇叭响声就是“滴滴滴”,继承父类alarm()方法即可
//所有汽车的行驶流程都是一致的,继承父类run()方法
}
/*
*具体实现类,奔驰汽车模型
*/
public class BENZModel extends AbstractCarModel{
//实现汽车抽象模板类的start()方法,使奔驰能够启动
@Override
protected void start() {
System.out.println("奔驰启动!");
}
//实现汽车抽象模板类stop()方法,使奔驰能够刹车
@Override
protected void stop() {
System.out.println("奔驰停车!");
}
//奔驰汽车喇叭响声就是“巴拉巴拉巴拉”,需要重写父类alarm()方法
@Override
protected void alarm(){
System.out.println("巴拉巴拉巴拉");
}
//所有汽车的行驶流程都是一致的,继承父类run()方法
}
/*
* 实现场景类
*/
public class Client {
public static void main(String[] args){
//做个宝马
BMWModel bmw = new BMWModel();
//开辆奔驰
BENZModel benz = new BENZModel();
//宝马跑一跑
bmw.run();
System.out.println("*************分割线*************");
//奔驰跑一跑
benz.run();
}
}
2、我们知道,不合理使用类的继承会破坏父类封装性(子类可能篡改父类实现),所以抽象类的正确使用姿势是:封装不变部分,扩展可变部分。如上例中,run()方法是被AbstractCarModel抽象类用final关键字封装好的,不允许篡改。而start()方法、stop()方法与alarm()方法都是可变的,子类去实现或者重写,合理地实现了扩展。
3、这其实就是一个简单的模板方法模式的体现,我在阿里巴巴面试的时候被问到:用接口不能实现,非抽象类实现不可的应用场景?我这时候就该回答模板方法设计模式~~~~(>_<)~~~~
下面详细介绍模板方法模式
模板方法模式的重点在于抽象模板类的方法类型,主要分三种:
1、抽象方法:由抽象类声明,用abstract关键字标识,由具体实现类去实现。如上例所示的start()方法与stop()方法。
2、具体方法:由抽象类声明并实现,用final关键字标识,在具体实现类中只能调用。如上例所示的run()方法。
3、钩子方法:由抽象类声明并实现,具体实现类可以继承抽象父类的默认实现,也可以根据具体情况进行修改扩展。如上例所示的alarm()方法,BMWModle类就是继承了AbstractCarModel抽象类alarm()方法的默认实现;而BENZModel类就是重写了alarm()方法。
HttpServlet中的钩子方法:
钩子方法经常是一个空的实现,因为一个实现类并不需要全部的方法,比如HttpServlet类中的doPost()、doGet()、doPut()、doDelete()、doHead()……为处理HTTP请求,每一种HTTP方法对应着一个do方法,这也是钩子方法默认的命名规则。这些方法在HttpServlet抽象类中都是以空实现的钩子方法存在的。在具体实现的时候,一般一个Servlet只需要处理某几个HTTP方法,在具体的servlet中就重写对应的几个do方法就可以了。
钩子方法和具体方法的存在使抽象类与接口具有了最主要的差异:
接口主要体现的是一种规范,实现接口的类只能去实现这种规范,但用户通过与统一接口对接实现了规范与实现的分离,极大地降低了模块间的耦合度;
而通过模板方法模式使用抽象类,可以继承某些具体方法,实现了规范并增加了代码的可重用性,而继承抽象方法与钩子方法使实现类可以灵活地扩展抽象模板类,接口并不能有这样的灵活扩展特性,是非常常用而有意义的一种设计模式!