抽象类和接口有什么区别?你还不知道吗?

313 阅读6分钟

抽象类和接口的主要区别?

抽象类和接口的设计的动机不同?

  • 接口的设计是 自上而下 的,我们知道某一类事物的行为,然后对这些行为进行约束,一些类需要有这些行为的时候,只需要实现即可。接口讲究的是 先定义接口(规范),然后实现接口
  • 抽象类的设计是 自下而上 的,我们实现了很多类,发现他们之中有共同的特性,然后将他们的共同特征抽取成一个抽象类,方便代码的复用。

细说接口

在Java中,接口中的成员(包括常量、抽象方法、静态方法和默认方法)

具体来说:

  1. 常量:接口中定义的常量默认为 public static final,可以被其他类直接访问。
  2. 抽象方法:接口中的抽象方法默认为 public abstract,必须由实现接口的类提供具体实现。
  3. 静态方法:在Java 8及以后版本,接口中的静态方法默认为 public static,可以直接通过接口名调用。
  4. 默认方法:在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 之后接口提供了默认方法

为什么要有默认方法呢?我们都知道平常我们一般定义的都是抽象方法,如果要实现接口的时候,必须要实现接口中的抽象方法。

接口是用来定义规范,约束行为的,但是有些行为,我是不是可以不要。也就是有些方法,我并不想实现。

这样使得接口不仅仅是方法的声明,还可以提供具体的实现。

细说抽象类

  1. 不能实例化:你不能直接创建抽象类的对象。只能通过它的子类来创建对象。
  2. 可以有构造函数:抽象类可以有构造函数,尽管它不能被实例化,但可以在其子类中调用。
  3. 可以包含成员变量:可以在抽象类中定义成员变量,并为其提供访问修饰符。

抽象类的应用场景之模板方法

模板方法的应用中使用到了抽象类。

什么是模板方法?

模板方法模式允许你在抽象类中定义算法的框架(一套通用的执行流程),而让子类实现具体的细节,从而达到代码复用和功能扩展的目的。

简单来说,大体流程都一样,只是在某些步骤不一样。(也就是可以在某些方法中修改执行的逻辑)

从实际场景中聊模板方法

举一个例子:

不知道你们是否用过在线执行代码的平台,就是哪种不用装开发环境,就可以执行简单的代码的平台

比如:tool.lu/coderunner/

菜鸟教程:www.cainiaojc.com/tool/

如果说我们要做一个这样子的平台,从前端上传代码,到执行代码,最后输出结果,流程是怎么样子的?

其实主要有这些步骤,以 Java 代码为例:

1)前端上传代码,将用户代码保存为文件

2)编译代码,得到 class 文件

3)执行代码,得到输出结果

4)收集输出结果

5)文件清理,释放空间

流程大致就是这样子的,但是有没有想过一个问题,就是这个代码是跑在哪里的?你自己的宿主机上?

先来说说,代码跑在自己的宿主机上有什么问题?

  1. 比如有人写一个 sleep(一万年),直接可以让程序卡死。
  2. 不断的占用内存空间,直到堆溢出
  3. 一些骚操作,把你l数据库的密码给读出来
  4. ..............

那么怎么解决?

将代码的运行环境,和宿主机隔离开,创建一个全新的环境,提供的用户使用。那么我们使用 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();
    }

输出结果:

总结

我们了解了接口和抽象类的不同点在哪里。以及接口和抽象类在项目中的应用场景。通过抽象类,我们引出的模板方法的使用,使用模板方法设计模式,大大的实现代码的复用,让我们的代码变得更加的优雅。好了,分享到结束了,如果觉得我写的还不错,记得给我三连哦,创作真的不容易,感谢大家的支持,谢谢!