郑州企业网站排名优化方法,精品在线开发网站建设,免费架设网站,公众号开发者权限怎么开简述
在面向对象编程中#xff0c;抽象类和接口是常被用到的语法概念#xff0c;是面向对象四大特性#xff0c;以及很多设计模式、设计思想、设计原则实现的基础。它们之间的区别是什么#xff1f;什么时候用接口#xff1f;什么时候用抽象类#xff1f;抽象类和接口存…简述
在面向对象编程中抽象类和接口是常被用到的语法概念是面向对象四大特性以及很多设计模式、设计思想、设计原则实现的基础。它们之间的区别是什么什么时候用接口什么时候用抽象类抽象类和接口存在的意义是什么等等 1.什么是抽象类和接口它们有什么区别
我们来看下 Java 语言中是如何定义抽象类的。下面这段代码是一个比较典型的抽象类的使用场景模板方法模式。Logger 是一个记录日志的抽象类FileLogger 和 MessageQueueLogger 继承 Logger 分别实现两种不同的日志记录方式记录日志到文件和记录日志到消息队列。FileLogger 和 MessageQueueLogger 两个子类复用了父类 Logger 中的 name、enabled、minPermittedLevel 属性和 log() 方法但是因为这两个子类的写日志方式不同它们又各种重写了父类中的 doLog() 方法。
// 抽象类
public abstract class Logger {private String name;private boolean enabled;private Level minPermittedLevel;public Logger(String name, boolean enabled, Level minPermittedLevel) {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(Level level,String message);
}// 抽象的子类输出日志到文件
public class FileLogger extends Logger {private Writer fileWriter;public FileLogger(String name, boolean enabled,Level minPermittedLevel, String filepath) {super(name, enabled, minPermittedLevel);this.fileWriter new FileWriter(filepath);}Overrideprotected void doLog(Level level, String message) {// 格式化level和message输出日志文件fileWriter.write(...);}
}// 抽象的子类输出日志到消息中间件比如Kafka
public class MessageQueueLogger extends Logger {private MessageQueueClient msgQueueClient;public MessageQueueLogger(String name, boolean enabled,Level minPermittedLevel, MessageQueueClient msgQueueClient) {super(name, enabled, minPermittedLevel);this.msgQueueClient msgQueueClient;}Overrideprotected void doLog(Level level, String message) {// 格式化level和message输出到消息中间件msgQueueClient.send(...);}
}通过上面这个例子我们来总结下抽象类的特性
抽象类不允许被实例化只能被继承。抽象类可以包含属性和方法。方法既可以包含代码实现比如 Logger 中的 log()也可以不包含代码实现比如 Logger 中的 doLog()。不包含代码实现的方法叫做抽象方法。子类继承抽象类必须实现抽象类中的所有抽象方法对应到例子代码中所有继承 Logger 的子类都必须重写 doLog()。
再来看下在 Java 语言中如何定义接口
// 接口
public interface Filter {void doFilter(RpcRequest req) throws RpcException;
}// 接口实现类鉴权过滤器
public class AuthFilter implements Filter {Overridepublic void doFilter(RpcRequest req) throws RpcException {// 鉴权逻辑...}
}// 接口实现类限流过滤器
public class RateLimitFilter implements Filter {Overridepublic void doFilter(RpcRequest req) throws RpcException {// 限流逻辑...}
}// 过滤器使用Demo
public class Application {private ListFilter filters new ArrayList();// filters.add(new AuthFilter());// filters.add(new RateLimitFilter());public void handleRpcRequest(RpcRequest req) {try {for (Filter filter : filters) {filter.doFilter(req);}} catch (RpcException e) {// 处理过滤异常...}// 省略其他逻辑...}
}上面这段代码是一个比较典型的接口的使用场景。我们通过 Java 语言中的 interface 关键字定义了一个 Filter 接口。AuthFilter 和 RateLimitFilter 是接口的两个实现类分别实现了对 RPC 请求鉴权和限流的过滤功能。
总结下接口的三个特性
接口不能包含属性接口只能申明方法方法不能包含代码实现。类实现接口的时候必须实现接口类中声明的所有方法。
从语法特性上对比两种有比较大的差别比如抽象类中可以定义属性、实现方法而接口中不能定义属性方法也不能实现。除了语法特性从设计角度两种也是由较大的区别的。
抽象类实际上是类只不过是一种特殊的类这种类不能被实例化为对象只能被子类继承。我们知道继承是一种 is-a 的的关系那抽象类既然属于类也表示一种 is-a 的关系。相对于抽象类的 is-a 来说接口表示一种 has-a 关系表示具有某些功能。对于接口有一种更加形象的叫法那就是协议contract。
2.抽象类和接口能解决什么问题
首先看一下为什么需要抽象类它能够解决什么编程问题
刚刚讲过抽象类不能实例化只能被继承。而继承能解决代付复用问题所以抽象类也是为代码复用而生的。多个子类可以继承抽象类中定义的熟悉和方法避免在子类在编写重复的代码。
既然继承本身能到到代码复用的目的那不用抽象类也能实现继承和复用。那抽象类除了解决代码复用的问题还有什么其他的意义吗
还是之前日志的例子来讲解。Logger 不再是抽象类而是一个普通的父类删除了 log()、doLog() 方法新增了 isLoggable() 方法。FileLogger 和 MessageQueueLogger 还是继承 Logger 以达到代码复用的目的。
// 父类非抽象类就是普通的类. 删除了log(),doLog()新增了isLoggable().
public class Logger {private String name;private boolean enabled;private Level minPermittedLevel;public Logger(String name, boolean enabled, Level minPermittedLevel) {//...构造函数不变代码省略...}public boolean isLoggable(Level level) {boolean loggable enabled (minPermittedLevel.intValue() level.intValue());return loggable;}
}// 子类输出日志到文件
public class FileLogger extends Logger {private Writer fileWriter;public FileLogger(String name, boolean enabled,Level minPermittedLevel, String filepath) {//...构造函数不变代码省略...}public void log(Level level, String message) {if (!isLoggable(level)) return;// 格式化level和message,输出到日志文件fileWriter.write(...);}
}
// 子类: 输出日志到消息中间件(比如kafka)
public class MessageQueueLogger extends Logger {private MessageQueueClient msgQueueClient;public MessageQueueLogger(String name, boolean enabled,Level minPermittedLevel, MessageQueueClient msgQueueClient) {//...构造函数不变代码省略...}public void log(Level level, String message) {if (!isLoggable(level)) return;// 格式化level和message,输出到消息中间件msgQueueClient.send(...);}
}这个涉及思路虽然达到了代码复用的目的但是无法使用多态特性了。像下面这样编写代码就会出现编译报错。
Logger logger new FileLogger(access-log, true, Level.WARN, /file/access.log);
logger.log(Level.ERROR, This is a test log message.);你可以能会说这个问题解决起来很简单啊在 Logger 中定义一个空的 log() 方法让子类重写父类的 log() 方法实现自己的记录日志的逻辑不就可以了吗
public class Logger {// 省略其他代码...public void log(Level level, String message) { //do nothing... }
}public class FileLogger extends Logger {// 省略其他代码...Overridepublic void log(Level level, String message) {if (!isLoggable(level)) return;// 格式化level和message,输出到日志文件fileWriter.write(...);}
}
public class MessageQueueLogger extends Logger {// 省略其他代码...Overridepublic void log(Level level, String message) {if (!isLoggable(level)) return;// 格式化level和message,输出到消息中间件msgQueueClient.send(...);}
}这个设计思路虽然可以用但是它显然没有之前通过抽象类的实现思路优雅。主要有以下几点原因
在 Logger 中定义一个空的 log() 方法会影响代码的可读性。如果我们不熟悉 Logger 背后的设计思想代码注释有不太好我们在阅读 Logger 代码的时候就可能对为什么定义一个空的 log() 方法感到疑惑需要查看 Logger 、FileLogger、MessageQueueLogger 之间的继承关系才能弄明白其设计意图。当创建一个新的子类继承 Logger 的时候我们可能会忘记重新实现 log() 方法。不像抽象类编译器会强制要求子类重写 log() 方法。你可能会说怎么可能会忘记重新实现呢 我们举个简单的例子如果 Logger 代码有几百行有 n 个方法这个时候你很有可能会忘记重写 log() 方法。Logger 可以被实例化换句话说我们可以 new 一个 Logger 出来并调用空的 log() 方法。这增加了类被误用的风险。当然这个问题可以通过设置私有的构造函数的方式来解决。不过显然没有通过抽象类来的优雅。
其次我们再来看一下我们为什么需要接口它能解决什么问题
抽象类更多的是为了代码复用而接口就侧重于解耦。接口是对行为的一种抽象相当于一组协议或契约你可以类比 API 接口。调用者只需要关注抽象的接口不需要了解具体的实现。接口实现了约定和实现分离可以降低代码间的耦合性提供代码的可扩展性。
实际上接口是一个比较抽象类更加广发、更加重要的知识点。比如我们经常提到的“基于接口而非实现编程”就是一条几乎天天会被用到并且能极大地提高代码的灵活性、扩展性的设计思想。
3. 如何决定该用抽象类还是接口
实际上判断标准很简单。如果我们要表示一种 is-a 的关系并且是为了解决代码复用的问题我们就用抽象类如果我们要表示一种 has-a 的关系并且是为了解决抽象而非代码复用的问题我们就用接口。
从类层次上来看抽象类是一种自下而上的设计思路现有子类的代付重复然后再抽象成上层的父类也就是抽象类。而接口正好相反它是一种自上而下的设计思路。我们在编程的时候一般都是先设计接口再去考虑具体实现。
总结
1.抽象类和接口的语法特性
抽象类不允许实例化只能被继承。它可以包含属性和方法。方法既可以包含代码实现也可以不包含代码实现。不包含代码实现的方法叫做抽象方法必须由子类实现。
接口不能包含属性只能声明方法方法不能包含代码实现。类实现接口的时候必须实现接口中申明的所有方法。
2.抽象类和接口的意义
抽象类是对成员变量和方法的抽象是一种 is-a 的关系是为了解决代付复用的问题。
接口仅是对方法的抽象是一种 has-a 的关系表示某一组行为特性是为了解决解耦问题隔离接口和具体的实现提高带代码的可扩展性。
3.抽象类和接口的应用场景
判断标准很简单。
如果要表示一种 is-a 的关系并且是为了解决代付复用的问题我们就用抽象类。
如果要表示一种 has-a 的关系并且是为了解决抽象而非代码复用问题我们就用接口。