接口和抽象类的区别
接口 | 抽象类 | |
---|---|---|
是否可以被实例化 | 否 | 否 |
是否可以写抽象方法 | 是 | 是 |
是否可以写普通方法 | 否 | 是 |
是否可以写 static 方法 | 是 | 是 |
是否可以写 default 方法 | 是 | 否 |
是否可以写属性 | 是 | 是 |
访问修饰符 | 都为 public | 可以自定义 |
为什么需要抽象类
抽象类描述了 is-a 的关系,表示一个类是什么,可以解决代码复用的问题。
典型的场景,通过 “模版方法” 模式,来提高代码复用性。很明显,要想使用 “模版方法” 模式,肯定需要抽象类。
这里举个例子:
/**
* 抽象的查询执行器类
*/
public abstract class AbstractFindExecutor{
public void find(){
// 1. 拼接查询字段
buildSelectFields();
// 2. 拼接查询条件
buildCriteria();
// 3. 查询 DB
doFind();
}
private String buildSelectFields() {
return "id, name";
}
private String buildCriteria() {
return "where id = '1q2w3e4r5t'";
}
protected abstract String doFind();
}
/**
* 分页查询的执行类
*/
public class FindPageExecutor extends AbstractFindExecutor {
/**
* 这里是分页查询特有的方法
*/
@Override
protected String doFind() {
buildPageable();
findPageDB();
return buildResult();
}
private String buildPageable() {
return "limit 30 offset 10";
}
private String buildResult() {
return "success";
}
private String findPageDB() {
return "find page";
}
}
这个例子中, AbstractFindExecutor
类中的 find 方法描述了一个查询过程。
其中分为三步:
- 拼接查询字段
- 拼接查询条件
- 查询 DB
其中第三步不同的查询方法执行的逻辑是不一样的,比如 “分页查询” 需要构造分页条件和需要组装分页返回数据。
这样子做的目的将查询中公用的方法提取到抽象类的 find 方法中。针对每种查询特有的方法,重写抽象方法实现自身子类特有的方法逻辑。
如果这里没有使用抽象类,那么原本抽象类中的 doFInd() 这个方法不能声明为抽象方法,只能声明一个空方法,且空方法中还需要 throw new UnsupportedOperationException()
,这样的实现不优雅。
为什么不优雅,因为子类没有实现这个空方法,也不会编译报错,不容易在编码的过程中发现问题,其实这又提升了 bug 的出现率。
为什么需要接口
接口描述了 has-a 的关系,表示一个类有某些能力。
使用接口也体现了 “面向接口编程而非实现”。这里体现的思想也就是之前提到的 “抽象”。因为面向接口编程后,就实现了约定和实现相分离,只要约定不变,实现无论怎么改变对调用方不感知,这样也和调用方解耦了,没有强关联。
接口和抽象类如何选择
其实也比较好区别的,因为抽象类描述 is-a 的关系,接口是描述 has-a 的关系
这里再解释一下,is-a 表示 A 类是 B 类的意思。has-a 表示 A 类具有 B 类(可能是个接口)的能力
抽象类是解决代码复用问题
接口是解决拓展问题的