抽象类和接口的主要区别?
抽象类和接口的设计的动机不同?
- 接口的设计是 自上而下 的,我们知道某一类事物的行为,然后对这些行为进行约束,一些类需要有这些行为的时候,只需要实现即可。接口讲究的是 先定义接口(规范),然后实现接口
- 抽象类的设计是 自下而上 的,我们实现了很多类,发现他们之中有共同的特性,然后将他们的共同特征抽取成一个抽象类,方便代码的复用。
细说接口
在Java中,接口中的成员(包括常量、抽象方法、静态方法和默认方法)
具体来说:
- 常量:接口中定义的常量默认为
public static final,可以被其他类直接访问。 - 抽象方法:接口中的抽象方法默认为
public abstract,必须由实现接口的类提供具体实现。 - 静态方法:在Java 8及以后版本,接口中的静态方法默认为
public static,可以直接通过接口名调用。 - 默认方法:在Java 8及以后版本,接口中的默认方法默认为
public,可以被实现类继承或重写。
1. 接口中定义常量的应用场景
你平常开发的过程中,在接口中定义常量是不是很少用到?
换个问题,你是不是经常定义 常量类, 枚举 来消除代码中的 魔法值
比如:
1 代表 男 ? 0 代表 女 ?
1 和 0 这种分不清的值,为了消除,我们一般最常用的就是定于 常量类 或者是 枚举
常量类:
public class UserConstant {
public static final Integer MAN = 1;
public static final Integer WOMAN = 0;
}
因为接口默认是 public static final,利用接口这个天然的特性,我直接可以偷懒不写 public static final
public interface UserConstant {
// 默认 public static final
Integer MAN = 1;
Integer WOMAN = 0;
}
2. Java 8 之后接口提供了默认方法
为什么要有默认方法呢?我们都知道平常我们一般定义的都是抽象方法,如果要实现接口的时候,必须要实现接口中的抽象方法。
接口是用来定义规范,约束行为的,但是有些行为,我是不是可以不要。也就是有些方法,我并不想实现。
这样使得接口不仅仅是方法的声明,还可以提供具体的实现。
细说抽象类
- 不能实例化:你不能直接创建抽象类的对象。只能通过它的子类来创建对象。
- 可以有构造函数:抽象类可以有构造函数,尽管它不能被实例化,但可以在其子类中调用。
- 可以包含成员变量:可以在抽象类中定义成员变量,并为其提供访问修饰符。
抽象类的应用场景之模板方法
模板方法的应用中使用到了抽象类。
什么是模板方法?
模板方法模式允许你在抽象类中定义算法的框架(一套通用的执行流程),而让子类实现具体的细节,从而达到代码复用和功能扩展的目的。
简单来说,大体流程都一样,只是在某些步骤不一样。(也就是可以在某些方法中修改执行的逻辑)
从实际场景中聊模板方法
举一个例子:
不知道你们是否用过在线执行代码的平台,就是哪种不用装开发环境,就可以执行简单的代码的平台
如果说我们要做一个这样子的平台,从前端上传代码,到执行代码,最后输出结果,流程是怎么样子的?
其实主要有这些步骤,以 Java 代码为例:
1)前端上传代码,将用户代码保存为文件
2)编译代码,得到 class 文件
3)执行代码,得到输出结果
4)收集输出结果
5)文件清理,释放空间
流程大致就是这样子的,但是有没有想过一个问题,就是这个代码是跑在哪里的?你自己的宿主机上?
先来说说,代码跑在自己的宿主机上有什么问题?
- 比如有人写一个 sleep(一万年),直接可以让程序卡死。
- 不断的占用内存空间,直到堆溢出
- 一些骚操作,把你l数据库的密码给读出来
- ..............
那么怎么解决?
将代码的运行环境,和宿主机隔离开,创建一个全新的环境,提供的用户使用。那么我们使用 Docker 就可以隔离宿主机了。
那么 Docker 中执行代码的流程又是怎么样子的?
1)前端上传代码,将用户代码保存为文件
2)编译代码,得到 class 文件
3)将编译好的 class 文件,上传到容器中,Docker 中执行代码
4)收集输出结果
5)文件清理,清除容器,释放空间
看到这里你是不是发现了,哦!除了第三步和第五步,大致的流程都一样。我们自己写一个抽象的方法,让其子类去继承对应的方法,针对自己子类的实现细节的不同,去重写父类的中的方法。共同的逻辑,直接复用父类的方法即可。这样子,大大的复用了代码。
模板方法的具体实现
这里来提供 Java 的伪代码,不做具体的实现。
定义一个抽象的方法,实现具体的流程:
public abstract class AbstractTemplate {
public void execute() {
// 1. 把用户的代码保存为文件
saveCodeToFile();
// 2. 编译代码
compileFile();
// 3. 执行代码
executeCode();
// 4. 收集整理输出结果
getOutput();
// 5. 文件清理
deleteFile();
}
public void saveCodeToFile() {
System.out.println("保存代码");
}
public void compileFile() {
System.out.println("编译代码");
}
public void executeCode () {
System.out.println("默认执行代码");
}
public void getOutput() {
System.out.println("获取输出结果");
}
public void deleteFile() {
System.out.println("文件清理");
}
}
Java 原生代码实现(在宿主机中执行代码)
public class JavaNaiveSandBox extends AbstractTemplate{
// 直接调用父类的方法
@Override
public void execute() {
super.execute();
}
}
使用Docker来执行代码,并重写执行代码和文件清理的逻辑
public class DockerSandBox extends AbstractTemplate{
/**
* 重写父类的方法,定义自己的逻辑
*/
@Override
public void executeCode() {
System.out.println("使用 docker 执行代码");
}
@Override
public void deleteFile() {
// 清理后文件后
super.deleteFile();
// 顺便在清理容器
System.out.println("顺便在清理容器");
}
}
测试输出:
public static void main(String[] args) {
JavaNaiveSandBox javaNaiveSandBox = new JavaNaiveSandBox();
javaNaiveSandBox.execute();
System.out.println("------------");
DockerSandBox dockerSandBox = new DockerSandBox();
dockerSandBox.execute();
}
输出结果:
总结
我们了解了接口和抽象类的不同点在哪里。以及接口和抽象类在项目中的应用场景。通过抽象类,我们引出的模板方法的使用,使用模板方法设计模式,大大的实现代码的复用,让我们的代码变得更加的优雅。好了,分享到结束了,如果觉得我写的还不错,记得给我三连哦,创作真的不容易,感谢大家的支持,谢谢!