当前位置: 首页 > news >正文

设计模式学习笔记 - 面向对象 - 5.接口和抽象类的区别

简述

在面向对象编程中,抽象类和接口是常被用到的语法概念,是面向对象四大特性,以及很多设计模式、设计思想、设计原则实现的基础。它们之间的区别是什么?什么时候用接口?什么时候用抽象类?抽象类和接口存在的意义是什么?等等


1.什么是抽象类和接口?它们有什么区别?

我们来看下 Java 语言中是如何定义抽象类的。下面这段代码是一个比较典型的抽象类的使用场景(模板方法模式)。Logger 是一个记录日志的抽象类,FileLoggerMessageQueueLogger 继承 Logger ,分别实现两种不同的日志记录方式:记录日志到文件和记录日志到消息队列。FileLoggerMessageQueueLogger 两个子类复用了父类 Logger 中的 nameenabledminPermittedLevel 属性和 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 List<Filter> 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 接口。AuthFilterRateLimitFilter 是接口的两个实现类,分别实现了对 RPC 请求鉴权和限流的过滤功能。

总结下接口的三个特性:

  • 接口不能包含属性
  • 接口只能申明方法,方法不能包含代码实现。
  • 类实现接口的时候,必须实现接口类中声明的所有方法。

从语法特性上对比,两种有比较大的差别,比如抽象类中可以定义属性、实现方法,而接口中不能定义属性,方法也不能实现。除了语法特性,从设计角度,两种也是由较大的区别的。

抽象类实际上是类,只不过是一种特殊的类,这种类不能被实例化为对象,只能被子类继承。我们知道继承是一种 is-a 的的关系,那抽象类既然属于类,也表示一种 is-a 的关系。相对于抽象类的 is-a 来说,接口表示一种 has-a 关系,表示具有某些功能。对于接口,有一种更加形象的叫法,那就是协议(contract)。

2.抽象类和接口能解决什么问题?

首先,看一下为什么需要抽象类?它能够解决什么编程问题?

刚刚讲过抽象类不能实例化,只能被继承。而继承能解决代付复用问题,所以抽象类也是为代码复用而生的。多个子类可以继承抽象类中定义的熟悉和方法,避免在子类在编写重复的代码。

既然,继承本身能到到代码复用的目的,那不用抽象类也能实现继承和复用。那抽象类除了解决代码复用的问题,还有什么其他的意义吗?

还是之前日志的例子,来讲解。Logger 不再是抽象类,而是一个普通的父类,删除了 log()doLog() 方法,新增了 isLoggable() 方法。FileLoggerMessageQueueLogger 还是继承 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() 方法感到疑惑,需要查看 LoggerFileLoggerMessageQueueLogger 之间的继承关系,才能弄明白其设计意图。
  • 当创建一个新的子类继承 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 的关系,并且是为了解决抽象而非代码复用问题,我们就用接口。

相关文章:

设计模式学习笔记 - 面向对象 - 5.接口和抽象类的区别

简述 在面向对象编程中&#xff0c;抽象类和接口是常被用到的语法概念&#xff0c;是面向对象四大特性&#xff0c;以及很多设计模式、设计思想、设计原则实现的基础。它们之间的区别是什么&#xff1f;什么时候用接口&#xff1f;什么时候用抽象类&#xff1f;抽象类和接口存…...

PolarDN MISC做题笔记

cat flag 使用01打开flag.png,发现图片尾部有padding的数据。D0 CF 11 E0 A1 B1 1A E1为office2007以前版本的文件头。将其另存为flag.doc,打开发现提示需要密码。&#xff08;可以注意到&#xff1a;D0CF11E0非常类似DOCFILE&#xff09; 使用john的office2john.py 提取hash …...

Web安全之浅见

备注&#xff1a;这是我在2017年在自己的网站上写的文章&#xff0c;今天迁移过来。 昨天去参加了公司组织的一个关于网络安全的培训&#xff0c;了解了很多关于网络安全方面的知识&#xff0c;也才意识到网络安全是一项极其重要的领域。 本篇文章主要聊聊Web安全。不过我对于网…...

企业安全建设工具推荐

全自动化挖洞&#xff0c;助力企业安全建设&#xff0c;一键实现域名扫描、IP 发现、端口扫描、服务识别、网站识别、漏洞探测、分析发现、合规检查。 使用方式&#xff1a; 录入目标企业名称即可开始使用 技术细节&#xff1a; 第一步&#xff1a;通过企业主体关联企业备案…...

力扣(leetcode)第455题分发饼干(Python)

455.分发饼干 题目链接:455.分发饼干 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i…...

隐私也要付费?Meta公司为收集用户数据再出“奇招”

Cybernews网站消息&#xff0c;有相关人士表示&#xff0c;如果欧洲数据保护委员会&#xff08;EDPB&#xff09;不明确指出Meta公司的“付费或同意”的模式违反了欧盟的隐私法规&#xff0c;那么这一模式很可能会被大规模复制&#xff0c;危及数百万欧洲公民的自由选择权。 自…...

Android14 InputManager-InputReader的处理

IMS启动时会调用InputReader.start()方法 InputReader.cpp status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });…...

web前端安全性——JSONP劫持

1、JSONP概念 JSONP(JSON with Padding)是JSON的一种“使用模式”&#xff0c;可用于解决主流浏览器的跨域数据访问的问题。由于同源策略&#xff0c;协议IP端口有任意不同都会导致请求跨域&#xff0c;而HTML的script元素是一个例外。利用script元素的这个开放策略&#xff0…...

从零开始学HCIA之广域网技术03

1、LCP中包含的报文类型 &#xff08;1&#xff09;Configure-Request&#xff08;配置请求&#xff09;&#xff0c;链路层协商过程中发送的第一个报文&#xff0c;该报文表明点对点双方开始进行链路层参数的协商。 &#xff08;2&#xff09; Configure-Ack&#xff08;配置…...

AI推介-大语言模型LLMs论文速览(arXiv方向):2024.01.01-2024.01.10

1.Pre-trained Large Language Models for Financial Sentiment Analysis 标题:用于金融情感分析的预训练大型语言模型 author:Wei Luo, Dihong Gong date Time:2024-01-10 paper pdf:http://arxiv.org/pdf/2401.05215v1 摘要&#xff1a; 金融情感分析是指将金融文本内容划分…...

Redis降低内存占用(二)分片结构

一、分区方法&#xff1a; 分片&#xff0c;也称为分区。Redis提供了多种分区实现方案: 1、哈希分区 2、区间分区 3、一致性哈希分区 4、虚拟分区 5、LUA脚本实现分片 二、...

vue大文件读取部分内容,避免重复加载大文件,造成流量浪费

使用场景&#xff1a;项目点云地图是pcd文件&#xff0c;但是文件可能上百兆&#xff0c;我需要获取到文件中的版本信息&#xff0c;跟本地的缓存文件做比较&#xff0c;如果不一致&#xff0c;才会加载整个文件。从而节省流量。 避免重复加载整个“.pcd文件&#xff0c;以最大…...

5G网络RedCap

RedCap&#xff1a;RedCap&#xff08;Reduced Capability&#xff09;&#xff0c;即“降低能力”。它是3GPP在5G R17阶段&#xff0c;针对速率、时延要求不高的5G应用场景&#xff0c;专门推出的一种新技术标准协议&#xff0c;旨在全面提升5G网络质量和覆盖率&#xff0c;也…...

vue+springboot登录与注册功能的实现

①首先写一个登录页面 <template> <div style"background-color: #42b983;display: flex;align-items: center;justify-content: center;height: 100vh"><div style"background-color: white;display: flex;width: 50%;height: 50%;overflow: h…...

数据结构D3作业

1. 2. 按位插入 void insert_pos(seq_p L,datatype num,int pos) { if(LNULL) { printf("入参为空&#xff0c;请检查\n"); return; } if(seq_full(L)1) { printf("表已满&#xff0c;不能插入\n"); …...

Spring框架@Autowired注解进行字段时,使用父类类型接收子类变量,可以注入成功吗?(@Autowired源码跟踪)

一、 前言 平常我们在使用spring框架开发项目过程中&#xff0c;会使用Autowired注解进行属性依赖注入&#xff0c;一般我们都是声明接口类型来接收接口实现变量&#xff0c;那么使用父类类型接收子类变量&#xff0c;可以注入成功吗&#xff1f;答案是肯定可以的&#xff01;…...

【springblade】springblade(bladeX) 数据权限失效原因分析

文章目录 数据权限接口权限 前言&#xff1a;最近博主在按照bladeX官方文档 配置数据权限 结果发现失效了&#xff0c;网上搜了一下没找到合适的答案&#xff0c;本着求人不如求己的精神&#xff0c;自己调试了一下发现了问题所在&#xff0c;也大致看了一下bladeX的权限逻辑。…...

单例模式的几种实现方式

在Java中&#xff0c;实现单例模式主要有几种方式&#xff1a;懒汉式、饿汉式、双重检查锁定、静态内部类和枚举。每种方式都有其特点和适用场景。 1. 饿汉式&#xff08;线程安全&#xff09; 饿汉式是最简单的一种实现方式&#xff0c;通过静态初始化实例&#xff0c;保证了…...

鸿蒙OS运行报错 ‘ToDoListItem({ item })‘ does not meet UI component syntax.

在学习harmonyOS时&#xff0c;原本是好好运行的。但是突然报错 ToDoListItem({ item }) does not meet UI component syntax. 一脸懵逼&#xff0c;以为是自己语法问题检查了半天也没问题。 网上搜索了一下&#xff0c;说把多余的js\map文件删除就行 才发现我的 鸿蒙的开…...

React18源码: reconciler执行流程

reconciler执行流程 1 &#xff09;概述 此处先归纳一下react-reconciler包的主要作用&#xff0c;将主要功能分为4个方面&#xff1a; 输入&#xff1a;暴露api函数&#xff08;如&#xff1a;scheduleUpdateOnFiber&#xff09;, 供给其他包&#xff08;如react包&#xff0…...

从海报生成实战出发:深度解析Canvas文本绘制的那些“坑”与高效解决方案

从海报生成实战出发&#xff1a;深度解析Canvas文本绘制的那些“坑”与高效解决方案 在数字化营销盛行的今天&#xff0c;一张精美的海报往往能成为内容传播的"门面担当"。无论是文章分享、活动推广还是品牌展示&#xff0c;视觉化呈现的效果直接影响用户点击意愿。…...

用ESP32-S3给OV2640摄像头上‘网课’:手把手实现低延迟MJPEG监控系统

基于ESP32-S3与OV2640构建低延迟MJPEG监控系统的工程实践 在物联网和边缘计算领域&#xff0c;实时视频监控系统的需求日益增长。本文将深入探讨如何利用ESP32-S3微控制器和OV2640摄像头模组构建一个完整的低延迟MJPEG监控系统&#xff0c;从硬件连接到软件优化&#xff0c;全…...

旧设备重生:用OpenCore Legacy Patcher实现Mac系统升级的完整指南

旧设备重生&#xff1a;用OpenCore Legacy Patcher实现Mac系统升级的完整指南 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 您的Mac是否因硬件限制无法升级到最新macOS系…...

手把手教你用Docker快速搭建CVE-2025-55182漏洞复现环境(附POC验证)

基于Docker的CVE-2025-55182漏洞靶场构建与安全研究实践 在当今快速迭代的前端技术生态中&#xff0c;React Server Components&#xff08;RSC&#xff09;作为Next.js框架的核心特性&#xff0c;正在重塑服务端渲染的实现方式。然而&#xff0c;2025年曝光的CVE-2025-55182漏…...

Wasmtime代码缓存机制:提升WebAssembly执行性能的终极指南

Wasmtime代码缓存机制&#xff1a;提升WebAssembly执行性能的终极指南 【免费下载链接】wasmtime A fast and secure runtime for WebAssembly 项目地址: https://gitcode.com/gh_mirrors/wa/wasmtime Wasmtime作为一款快速且安全的WebAssembly运行时&#xff0c;其代码…...

MoneyPrinterPlus未来路线图深度解析:AI短视频生成工具的终极进化指南 [特殊字符]

MoneyPrinterPlus未来路线图深度解析&#xff1a;AI短视频生成工具的终极进化指南 &#x1f680; 【免费下载链接】MoneyPrinterPlus 使用AI大模型技术,一键批量生成各类短视频,自动批量混剪短视频,自动把视频发布到抖音,快手,小红书,视频号上,赚钱从来没有这么容易过! Generat…...

总担心家人生病?心理学教你摆脱 “灾难化思维”

父母晚回半小时&#xff0c;孩子轻微咳嗽&#xff0c;伴侣说头晕…… 你是不是瞬间脑补出无数可怕画面&#xff0c;越想越慌&#xff0c;直到拨通电话才安心&#xff1f;这不是矫情&#xff0c;是灾难化思维在作祟。一、为什么总担心家人生病&#xff1f;3个深层根源对失去的恐…...

三相静止无功发生器SVG并网仿真模型(附说明报告) 仿真带一份与仿真完全对应的31页Word报...

三相静止无功发生器SVG并网仿真模型&#xff08;附说明报告&#xff09; 仿真带一份与仿真完全对应的31页Word报告可结合仿真快速入门学习SVG [1]报告内容&#xff1a;原理说明及仿真详细说明和结果分析&#xff08;详细看展示的报告内容&#xff09; 1.基于电压定向的双闭环控…...

15天深度体验:micro编辑器状态栏系统监控完全指南

15天深度体验&#xff1a;micro编辑器状态栏系统监控完全指南 【免费下载链接】micro A modern and intuitive terminal-based text editor 项目地址: https://gitcode.com/gh_mirrors/mi/micro micro编辑器是一款现代化的终端文本编辑器&#xff0c;以其直观易用和高度…...

OpenClaw安全防护指南:Qwen3-32B镜像对接时的权限控制策略

OpenClaw安全防护指南&#xff1a;Qwen3-32B镜像对接时的权限控制策略 1. 为什么需要安全防护&#xff1f; 去年我在尝试用OpenClaw自动整理财务报表时&#xff0c;曾因为一个简单的"读取桌面所有Excel文件"指令&#xff0c;差点导致包含客户隐私的文档被误传到测试…...