1. 接口和抽象类的特性
抽象类
- 抽象类不允许被实例化,只能被继承
- 抽象类必须有抽象方法
- 子类继承抽象类必须实现抽象类中的所有抽象方法
接口
- 接口只能定义常量不能定义属性
- 接口只能声明方法,不能实现方法
- 实现接口的类必须实现接口中声明的所有方法
接口和抽象类的不同点
- 语法特性:相比抽象类,接口中不能定义属性,不能实现方法。
- 设计角度:抽象类和子类表示的是 is-a 关系,接口和实现类表示的是 has-a 关系,即具有某些功能。接口的更形象的称呼:协议(contract)
接口和抽象类的意义
- 相比普通类,抽象类除了可以实现代码复用,亦可以实现多态,其中模板设计模式就是以抽象类为基础,从设计上而言很有美感。如下面这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//抽象类
public abstract class Logger {
private String name;
private boolean enabled;
private Level minPermittedLevel;
public Logger(Stringname,booleanenabled,LevelminPermittedLevel){
this.name=name;
this.enabled=enabled;
this.minPermittedLevel=minPermittedLevel;
}
public void log(Level level, String message){
boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
if(!loggable) return;
doLog(level, message);
}
protected abstract void doLog(Levellevel,Stringmessage);
}
//抽象类的子类:输出日志到文件
public class FileLogger extends Logger{
private Writer fileWriter;
public FileLogger(String name,boolean enabled,Level minPermittedLevel,String filepath){
super(name, enabled, minPermittedLevel);
this.fileWriter = newFileWriter(filepath);
}
@Override
public void doLog(Level level, String mesage){
//格式化level和message,输出到日志文件
fileWriter.write(...);
}
}
//抽象类的子类:输出日志到消息中间件(比如kafka)
public class Message QueueLogger extends Logger{
private MessageQueueClient msgQueueClient;
publicMessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQueueClient msgQueueClient){
super(name, enabled, minPermittedLevel);
this.msgQueueClient = msgQueueClient;
}
@Override
protectedvoiddoLog(Level level, String mesage){
//格式化level和message,输出到消息中间件
msgQueueClient.send(...);
}
}
- 相比抽象类,接口更多用于实现多态特性,用于解耦,提高程序的可扩展性。如下面这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//接口
public interface Filter{
void doFilter(RpcRequest req) throws RpcException;
}
// 接口实现类:鉴权过滤器
public class AuthencationFilter implements Filter {
@Override
public void doFilter(RpcRequest req) throws RpcException {/**...鉴权逻辑..**/}
}
// 接口实现类:限流过滤器
public class RateLimitFilter implements Filter {
@Override
public void doFilter(RpcRequest req) throws RpcException {/**...限流逻辑...**/}
}
// 过滤器使用Demo
public class Application {
private List<Filter> filters = new ArrayList<>();
filters.add(new AuthencationFilter());
filters.add(new RateLimitFilter());
public void handleRpcRequest(RpcRequest req) {
try {
for (Filter filter : filters) {
filter.doFilter(req);
}
} catch(RpcException e) {/**...处理过滤结果...**/}
// ...省略其他处理逻辑...
}
}
2. 什么时候用接口?什么时候用抽象类?
- 实际上,判断的标准很简单。如果我们要表示一种is-a的关系,并且是为了解决代码复用的问题,我们就用抽象类;如果我们要表示 一种has-a关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。
- 从类的继承层次上来看,抽象类是一种自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(也就是抽象类)。而接口正好相反,它是一种自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再去考虑具体的实现。
3. 设计原则:基于接口而非实现编程
首先这条原则是先于 Java 诞生的,因而此 ‘接口’ 非彼‘接口’,实际上,该原则的另一个表述方式是“基于抽象而非实现编程”,这样更能体现该原则的设计初衷。
理解接口的定义
“接口”就是一组“协议”或者“约定”,是功能提供者提供给使用者的一个“功能列表”。
设计初衷
将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。
如何应用这条原则
- 函数的命名不能暴露任何实现细节。
- 封装具体的实现细节
- 为实现类定义抽象的接口。
4. 多用组合少用继承
什么是组合
参考监听者模式,被监听者类里组合监听者作为它的一个属性,这就是组合
为什么多用组合少用继承
继承最大的问题就在于:继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性,如下面这张继承图:
该设计旨在抽象现实中的鸟类,而现实往往千变万化,比如除会飞的鸟、不会飞的鸟,还有下蛋的不下蛋的,食肉的和不食肉的等等,这样就会导致类继承关系很复杂,类的数量也会急剧增加
使用方法
实际上,我们可以利用组合(composition)、接口、委托(delegation)三个技术手段一块儿来解决刚刚继承存在的问题。像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Flyable {
void fly();
}
public class FlyAbility implements Flyable {
@Override
public void fly() { /**...**/ }
}
public class Ostrich implements Flyable {//鸵鸟
private FlyAbility flyAbility = new FlyAbility(); //组合
@Override
public void tweet() {
tweetAbility.tweet(); // 委托
}
@Override
public void layEgg() {
eggLayAbility.layEgg(); // 委托
}
}
替换组合
继承主要的三个作用都可用其他手段替换:
- 表示is-a关系,可通过组合和接口的has-a关系来替代
- 支持多态特性,可以利用接口来实现
- 代码复用,可以通过组合和委托来实现
少用继承不是完全不用继承?
继承改写成组合意味着要做更细粒度的类的拆分,即要定义更多的类和接口。类和接口的增多必然增加代码的复杂程度和维护成本。要根据具体的情况,来选择继承还是组合。如:
- 类继承关系最多2层,就大胆用继承
- 类与类之间没有继承关系,即为了实现代码复用没必要抽象一个父类,这时候就大胆用组合吧(搞一个工具类啥的)
- 函数参数强制为类而不是接口,这个时候为了实现多态就只能使用继承重写对应的方法