【6大设计原则】精通设计模式之里氏代换原则:从理论到实践,掌握代码演化的黄金法则
一、引言
1.1 设计模式的必要性
在软件开发的复杂性面前,设计模式提供了一套成熟的解决方案,它们是经过多年实践总结出来的,能够帮助我们应对各种编程难题。设计模式不仅仅是一种编程技巧,更是一种编程哲学,它能够提高代码的可读性、可维护性和可扩展性,使代码更加健壮。在现代软件开发中,不懂设计模式就像不懂语法一样,是难以想象的。
1.2 六大设计原则简介

六大设计原则是面向对象设计的基础,它们是:单一职责原则、开放封闭原则、里氏代换原则、接口隔离原则、依赖倒置原则和迪米特法则。这些原则是面向对象设计的核心,掌握它们能够使我们的代码更加简洁、清晰、易于维护。每一条原则都有其深刻的含义和实际的应用场景,是软件设计中不可或缺的指导方针。
1.3 里氏代换原则的重要性
里氏代换原则是面向对象设计中最重要的原则之一,它要求我们在设计类的时候,要遵循一条基本规则:子类必须能够替换掉它们的基类,而不会引起程序的非预期行为。这条原则看似简单,实则包含了深刻的含义。它不仅是实现开闭原则的基础,也是实现其他设计原则的前提。通过遵循里氏代换原则,我们可以创建出更加灵活、可扩展的代码结构,使代码更加符合面向对象的设计理念。
二、里氏代换原则理论解析

2.1 定义与内涵
里氏代换原则(Liskov Substitution Principle, LSP)是由Bertrand Meyer提出的面向对象设计的基本原则之一。它规定:如果S是一个类,那么任何S的子类都应当是S的一个实例的“替代品”。这意味着,在程序中,我们应该能够用子类对象替换掉基类对象,而不会导致程序的行为出现异常。换句话说,基类的方法应该被设计成能够被其子类的所有实例所替换,而不需要修改代码。
2.2 原理与动机
里氏代换原则的原理在于,它鼓励我们在设计类时,应该关注类的抽象,而不是具体的实现。这样,当我们需要对类进行扩展时,就可以通过创建新的子类来完成,而不是直接修改基类。这种设计方式有助于减少代码的耦合度,提高代码的可维护性和可扩展性。
动机的背后是面向对象设计中的一个基本矛盾:一方面,我们希望类的功能是封闭的,即一个类应该只关注自己的业务逻辑,而不关心其他类的细节;另一方面,我们希望类的功能是可扩展的,即在不修改原有代码的情况下,能够方便地对类进行扩展。里氏代换原则正是为了解决这个矛盾而提出的。
2.3 面向对象的基本概念
为了更好地理解里氏代换原则,我们需要回顾一些面向对象的基本概念:
- 类(Class):类是对象的蓝图,它定义了一组属性(称为“字段”)和方法(称为“行为”)。
- 对象(Object):对象是类的实例,它具有类定义的属性和方法。
- 继承(Inheritance):继承是面向对象编程中的一个核心概念,它允许我们创建一个新的类(子类),该类继承了另一个类(基类)的属性和方法。
- 子类(Subclass):子类是继承自某个基类的类,它继承了基类的所有属性和方法,并可以添加新的属性和方法,或者覆盖基类的方法。
- 基类(Base Class):基类是被继承的类,它提供了子类可以继承的属性和方法。
通过理解这些基本概念,我们可以更好地理解里氏代换原则的重要性,以及如何在实际编程中应用它。在下一节中,我们将通过具体的代码实例来演示里氏代换原则的应用。
三、里氏代换原则实例解析
3.1 案例一:违反里氏代换原则的代码
在这个案例中,我们将看到一个违反里氏代换原则的类设计。假设我们有一个形状接口,以及两个实现该接口的类:圆形和正方形。我们希望通过形状接口来操作这些形状,但是,如果我们的代码是这样实现的:
public interface Shape {double getArea();
}public class Circle implements Shape {private double radius;public Circle(double radius) {this.radius = radius;}public double getArea() {return Math.PI * radius * radius;}
}public class Rectangle implements Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}public double getArea() {return width * height;}
}// 使用形状接口操作形状
public class ShapeOperations {public void draw(Shape shape) {System.out.println("Drawing " + shape.getClass().getSimpleName());}
}public class Main {public static void main(String[] args) {ShapeOperations operations = new ShapeOperations();Circle circle = new Circle(5);Rectangle rectangle = new Rectangle(4, 5);operations.draw(circle);operations.draw(rectangle);}
}
在这个例子中,ShapeOperations 类有一个 draw 方法,它接受一个 Shape 接口的实例作为参数。这看起来很不错,但是,如果我们想要添加一个新的形状,比如椭圆,我们不得不修改 Shape 接口,因为椭圆既不是圆形也不是矩形。这就违反了里氏代换原则,因为基类 Shape 应该能够被其子类的任何实例所替换。
3.2 案例二:符合里氏代换原则的代码
为了修复上一个案例中的问题,我们可以重新设计 Shape 接口和相关的类。这次,我们会使用里氏代换原则来指导我们的设计。
public interface Shape {double getArea();
}public abstract class AbstractShape implements Shape {// 抽象方法,由子类实现@Overridepublic abstract double getArea();
}public class Circle extends AbstractShape {private double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double getArea() {return Math.PI * radius * radius;}
}public class Rectangle extends AbstractShape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double getArea() {return width * height;}
}// 新增的椭圆类
public class Ellipse extends AbstractShape {private double majorRadius;private double minorRadius;public Ellipse(double majorRadius, double minorRadius) {this.majorRadius = majorRadius;this.minorRadius = minorRadius;}@Overridepublic double getArea() {return Math.PI * majorRadius * minorRadius;}
}public class ShapeOperations {public void draw(Shape shape) {System.out.println("Drawing " + shape.getClass().getSimpleName());}
}public class Main {public static void main(String[] args) {ShapeOperations operations = new ShapeOperations();Circle circle = new Circle(5);Rectangle rectangle = new Rectangle(4, 5);Ellipse ellipse = new Ellipse(3, 2);operations.draw(circle);operations.draw(rectangle);operations.draw(ellipse);}
}
在这个改进的例子中,我们创建了一个抽象类 AbstractShape,它实现了 Shape 接口并提供了 getArea 方法的抽象实现。这样,当我们想要添加一个新的形状时,我们只需要创建一个新的子类来实现 AbstractShape 类,而不需要修改现有的 Shape 接口。这符合里氏代换原则,因为 ShapeOperations 类可以接受任何 AbstractShape 的子类实例,而不会影响现有的代码。
3.3 案例对比与分析
通过对比两个案例,我们可以清楚地看到里氏代换原则的重要性。在第一个案例中,由于违反了里氏代换原则,我们无法在不修改 Shape 接口的情况下添加新的形状。而在第二个案例中,由于遵循了里氏代换原则,我们能够轻松地添加新的形状,而不影响现有的类和代码。
四、里氏代换原则在实际项目中的应用
4.1 重构现有代码
在实际的软件开发过程中,我们经常会遇到需要重构代码的情况。重构的目的是提高代码的质量,使其更加清晰、简洁和可维护。里氏代换原则在这个过程中起着重要的作用。以下是一个重构的例子:
假设我们有一个 Animal 类,它有两个子类 Dog 和 Cat。现在我们想要给 Animal 类添加一个新的方法 makeSound。但是,由于 Dog 和 Cat 类都有不同的叫声,直接在 Animal 类中添加 makeSound 方法会导致代码的不一致性。这时,我们可以利用里氏代换原则来重构代码。
public interface Animal {// 接口中只定义方法,不具体实现
}public class Dog implements Animal {// Dog 类实现 Animal 接口
}public class Cat implements Animal {// Cat 类实现 Animal 接口
}// 重构后的 Animal 类
public abstract class AbstractAnimal implements Animal {// 抽象方法,由子类实现
}public class Dog extends AbstractAnimal {@Overridepublic void makeSound() {System.out.println("Woof woof");}
}public class Cat extends AbstractAnimal {@Overridepublic void makeSound() {System.out.println("Meow meow");}
}
通过重构,我们创建了一个抽象的 AbstractAnimal 类,它实现了 Animal 接口并提供了 makeSound 方法的抽象实现。这样,我们就能够在不修改 Dog 和 Cat 类的情况下,给 Animal 类添加一个新的方法。这符合里氏代换原则,因为 Dog 和 Cat 类都能够替换 Animal 类,而不会影响现有的代码。
4.2 设计新的类和方法
在设计新的类和方法时,遵循里氏代换原则是非常重要的。它能够帮助我们创建出更加灵活和可扩展的代码结构。以下是一个遵循里氏代换原则设计新的类和方法的例子:
public interface Payment {double calculateAmount(double price);
}public class CashPayment implements Payment {@Overridepublic double calculateAmount(double price) {return price;}
}public class CreditCardPayment implements Payment {@Overridepublic double calculateAmount(double price) {// 假设信用卡支付需要额外收取 5% 的费用return price * 1.05;}
}// 可以使用 Payment 接口来处理不同的支付方式
public class Order {private List<Payment> payments = new ArrayList<>();public void addPayment(Payment payment) {payments.add(payment);}public double getTotalAmount() {double total = 0;for (Payment payment : payments) {total += payment.calculateAmount(total);}return total;}
}
在这个例子中,我们定义了一个 Payment 接口,它有一个 calculateAmount 方法。然后,我们创建了两个实现 Payment 接口的类:CashPayment 和 CreditCardPayment。这样,我们就可以使用 Payment 接口来处理不同的支付方式,而不需要修改 Order 类的代码。这符合里氏代换原则,因为 CashPayment 和 CreditCardPayment 类都能够替换 Payment 类,而不会影响现有的代码。
4.3 测试与验证
在软件开发过程中,测试是非常重要的一个环节。里氏代换原则可以帮助我们编写更加可靠和易于测试的代码。以下是一个使用里氏代换原则进行测试的例子:
public class PaymentTest {@Testpublic void testOrderTotalWithCashPayment() {Order order = new Order();order.addPayment(new CashPayment());order.addPayment(new CashPayment());double total = order.getTotalAmount();Assert.assertEquals(200, total);}@Testpublic void testOrderTotalWithCreditCardPayment() {Order order = new Order();order.addPayment(new CreditCardPayment());order.addPayment(new CreditCardPayment());double total = order.getTotalAmount();Assert.assertEquals(210, total);}
}
在这个例子中,我们使用了 JUnit 测试框架来编写测试用例。我们分别测试了使用现金支付和信用卡支付的情况下,订单的总金额是否正确。由于我们遵循了里氏代换原则,我们可以使用 Payment 接口来测试不同的支付方式,而不会影响测试的可靠性。
五、里氏代换原则的灵活运用
5.1 应对复杂场景
在实际项目中,我们经常会遇到复杂的场景,这时候里氏代换原则的灵活运用就显得尤为重要。以下是一个应对复杂场景的例子:
假设我们有一个 Person 类,它有两个子类 Employee 和 Student。现在我们想要创建一个 Payroll 类,用于处理员工的工资计算。但是,我们很快发现,Employee 类和 Student 类在工资计算方面有很大的不同,直接使用 Person 类作为基类会导致代码的复杂性和不灵活性。
public interface Person {// 定义公共属性String getName();
}public class Employee implements Person {private double salary;public Employee(double salary) {this.salary = salary;}@Overridepublic String getName() {// 获取员工姓名}
}public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic String getName() {// 获取学生姓名}
}public class Payroll {private Person person;public Payroll(Person person) {this.person = person;}public double calculatePay() {return person.getName().equals("Employee") ? person.getSalary() : 0;}
}
在这个例子中,我们直接使用 Person 类作为基类,导致 Payroll 类中的 calculatePay 方法需要根据传入的 Person 对象来判断是 Employee 还是 Student,从而计算工资。这样,如果将来添加新的子类,比如 Teacher,我们不得不修改 Payroll 类的代码。
为了解决这个问题,我们可以将 Person 类改为一个抽象类,并提供一个 getPayAmount 抽象方法,让子类实现自己的工资计算逻辑。这样,Payroll 类就不需要关心具体的工资计算逻辑,从而更加灵活和可扩展。
public abstract class AbstractPerson implements Person {// 定义公共属性@Overridepublic abstract double getPayAmount();
}public class Employee extends AbstractPerson {private double salary;public Employee(double salary) {this.salary = salary;}@Overridepublic String getName() {// 获取员工姓名}@Overridepublic double getPayAmount() {return salary;}
}public class Student extends AbstractPerson {private String name;public Student(String name) {this.name = name;}@Overridepublic String getName() {// 获取学生姓名}@Overridepublic double getPayAmount() {return 0; // 学生没有工资}
}public class Payroll {private Person person;public Payroll(Person person) {this.person = person;}public double calculatePay() {return person.getPayAmount();}
}
通过将 Person 类改为一个抽象类,并提供一个 getPayAmount 抽象方法,我们使得 Payroll 类更加灵活和可扩展。这样,无论将来添加什么新的子类,Payroll 类都可以正确地处理工资计算。
5.2 与其他设计原则的配合
里氏代换原则是面向对象设计中的一个基本原则,但它并不是孤立存在的。它需要与其他设计原则相互配合,才能发挥出最大的效果。以下是一个与其他设计原则配合使用的例子:
public interface Animal {void makeSound();
}public class Dog implements Animal {@Overridepublic void makeSound() {System.out.println("Woof woof");}
}public class Cat implements Animal {@Overridepublic void makeSound() {System.out.println("Meow meow");}
}public class AnimalSound {private Animal animal;public AnimalSound(Animal animal) {this.animal = animal;}public void playSound() {if (animal instanceof Dog) {((Dog) animal).makeSound();} else if (animal instanceof Cat) {((Cat) animal).makeSound();}}
}
在这个例子中,我们使用里氏代换原则创建了 Animal 接口和两个实现该接口的类:Dog 和 Cat。然后,我们使用单一职责原则创建了一个 AnimalSound 类,它有一个 playSound 方法,用于播放不同动物的叫声。这样,我们通过遵循里氏代换原则和其他设计原则,创建了一个更加灵活和可维护的代码结构。
5.3 里氏代换原则的局限性

虽然里氏代换原则是面向对象设计中的一个重要原则,但它并不是万能的。在某些情况下,它可能会带来一些限制和局限性。以下是一些里氏代换原则的局限性:
- 接口泛滥:如果一个类有太多的接口,那么可能会导致接口泛滥,使代码变得复杂和不清晰。在这种情况下,可以考虑使用多重继承或者组合的方式来解决这个问题。
- 子类职责过重:如果一个子类承担了过多的职责,那么可能会导致子类变得过于复杂,难以维护和扩展。在这种情况下,可以考虑将子类的职责拆分成更小的类,或者使用组合的方式来实现。
- 动态类型安全:在某些情况下,如Java虚拟机(JVM)中,编译器可能无法完全检查出违反里氏代换原则的代码。在这种情况下,需要通过代码审查和测试来确保代码的质量和正确性。
六、总结
6.1 里氏代换原则的核心价值
里氏代换原则是面向对象设计中的一个核心原则,它强调了继承复用性的重要性。通过遵循里氏代换原则,我们可以创建出更加灵活和可扩展的代码结构,使得代码更加易于维护和扩展。它鼓励我们在设计类时,关注类的抽象和通用性,而不是具体的实现细节。这样,当我们需要对类进行扩展时,就可以通过创建新的子类来完成,而不是直接修改基类。这有助于减少代码的耦合度,提高代码的可维护性和可扩展性。
6.2 面向对象设计的重要性
面向对象设计是现代软件开发中的一项基本技能。它不仅可以帮助我们创建出更加灵活和可维护的代码结构,还能够提高我们的编程效率和代码质量。面向对象设计的核心是封装、继承和多态,它们共同构成了面向对象编程的基础。通过使用这些概念,我们可以创建出更加模块化、可重用和易于测试的代码。
相关文章:
【6大设计原则】精通设计模式之里氏代换原则:从理论到实践,掌握代码演化的黄金法则
一、引言 1.1 设计模式的必要性 在软件开发的复杂性面前,设计模式提供了一套成熟的解决方案,它们是经过多年实践总结出来的,能够帮助我们应对各种编程难题。设计模式不仅仅是一种编程技巧,更是一种编程哲学,它能够提…...
国内服务器安装Docker提示Failed to connect to download.docker.com port 443的解决方案
解决方案 换国内镜像源。我用的是清华的。https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ 自己找自己对应的版本。 例如你的Ubuntu系统。就用下列命令 sudo curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg -o /etc/apt/keyrings/do…...
前端开发攻略---彻底弄懂跨域解决方案
目录 1、浏览器的同源策略 1.1 源 1.2 同源与非同源 1.3 同源请求与非同源请求 2、跨域受到的限制 3、注意点 4、CORS解决Ajax跨域问题 4.1 CORS概述 4.2 CORS解决简单请求跨域 4.3 简单请求与复杂请求 4.4 CORS解决复杂请求跨域 4.5 借助CORS库快速完成配置 5、JS…...
【HeadFirst 设计模式】装饰者模式的C++实现
一、案例背景 Starbuzz是以扩张速度最快而闻名的咖啡连锁店。如果你在街角看到它的店,在对面街上肯定还会看到另一家。因为扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原先的类设计是这样的…… 购买咖啡时&am…...
大白话解释TCP的三次握手和四次挥手
你好,我是沐爸,欢迎点赞、收藏和关注。个人知乎 TCP的三次握手是浏览器与服务器建立连接的过程,而四次挥手,是两者断开连接的过程。今天把客户端和服务端当做两个人,通过打电话的方式解释连接建立和断开的过程。 TCP…...
asyncua模块实现OPC UA通讯
asyncua是OPCUA的python实现,使用起来非常方便,其github地址是https://github.com/FreeOpcUa/opcua-asyncio UaExpert是OPC UA Client的GUI工具,当编写好server代码后并运行,我们可以使用UaExpert去和server进行通信。UaExpert使…...
RabbitMQ的核心概念
RabbitMQ是一个消息中间件,也是一个生产者消费者模型,负责接收,存储和转发消息。 核心概念 Producer 生产者,是RabbitMQ Server的客户端,向RabbitMQ发送消息。 Consumer 消费者,是RabbitMQ Server的客…...
【vSphere 7/8】深入浅出 vSphere 证书 Ⅰ—— 初识和了解 vSphere证书
目录 摘要1. vSphere 安全证书1.1 vSphere 安全证书的类型和有效期 2. 在 vSphere Client 中初识 vSphere 证书2.1 vCenter 8.0.3 的 vSphere Client 界面2.2 vCenter Server 7.0 Update2 到 vCenter Server 8.0 Update 2 的 vSphere Client 界面2.3 vCenter Server 7.0 到 vCe…...
【云备份】服务端模块-热点管理
文章目录 0.回顾extern1.介绍2.实现思想3.代码测试代码 热点管理总结 0.回顾extern extern cloudBackup::DataManager *_dataManager extern 关键字用于声明一个全局变量或对象,而不定义它。这意味着 _dataManager 是一个指向 cloudBackup::DataManager 类型的指针…...
call apply bind特性及手动实现
call // 原生的call var foo { value: 1 };function bar(...args) {console.log("this", this.value, args); }bar.call(foo)// call 改变了bar的this指向 // bar函数执行了 // 等价于 // var foo { // name: "tengzhu", // sex: "man", …...
pygame开发课程系列(5): 游戏逻辑
第五章 游戏逻辑 在本章中,我们将探讨游戏开发中的核心逻辑,包括碰撞检测、分数系统和游戏状态管理。这些元素不仅是游戏功能的关键,还能显著提升游戏的趣味性和挑战性。 5.1 碰撞检测 碰撞检测是游戏开发中的一个重要方面,它用…...
嵌入式系统实时任务调度算法优化与实现
嵌入式系统实时任务调度算法优化与实现 目录 嵌入式系统实时任务调度算法优化与实现 引言 1.1 嵌入式系统的重要性 1.2 实时任务调度的重要性 实时任务的定义与分类 2.1 实时任务的定义 2.2 实时任务的分类 2.3 实时任务的其他分类方法 硬实时与软实时系统 3.1 硬实…...
Java:枚举转换
在Java中,你可以使用Enum.valueOf()方法将字符串转换为枚举常量。但是,如果你想要将枚举转换为其他类型,你需要自定义转换方法。以下是一个简单的例子,演示如何将枚举转换为整数: public enum Color {RED(1), GREEN(2…...
Vue、react父子组件生命周期
Vue 的父子组件生命周期 以下分为三部分,加载渲染阶段——更新阶段——销毁阶段,我们来一一介绍: 1、加载渲染阶段 在加载渲染阶段,一定得等子组件挂载完毕后,父组件才能挂载完毕,所以父组件的 mounted 在…...
HTML 基础要素解析
目录 HTML 初步认识 纯文本文件介绍 纯文本文件与其它文件的区别 Html介绍 HTML 骨架 文档类型(!DOCTYPE)声明 介绍 常用的 DOCTYPE 声明 meta标签 字符集 关键字和页面描述 HTML 初步认识 纯文本文件介绍 纯文本文件指的是仅包含文本内容&am…...
开源的向量数据库Milvus
Milvus是一款开源的向量数据库,专为处理向量搜索任务而设计,尤其擅长处理大规模向量数据的相似度检索。 官网地址:https://milvus.io/ 以下是关于Milvus的详细介绍: 一、基本概念 向量数据库:Milvus是一款云原生向量…...
设计模式-工厂方法
“对象创建”模式 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。典型模式 Factory MethodAbstract Factory…...
Flask SQLALchemy 的使用
Flask SQLALchemy 的使用 安装 Flask-SQLAlchemy配置 Flask-SQLAlchemy定义模型创建数据库和表插入和查询数据更新和删除数据迁移数据库总结Flask-SQLAlchemy 是一个 Flask 扩展,它简化了 Flask 应用中 SQLAlchemy 的使用。SQLAlchemy 是一个强大的 SQL 工具包和对象关系映射(…...
Metasploit漏洞利用系列(一):MSF完美升级及目录结构深度解读
在信息安全领域,MetasploitFramework(MSF)是一个无处不在的工具,它集合了大量的渗透测试和漏洞利用模块,帮助安全专家识别和利用系统中的弱点。本文将深入探讨如何对Metasploit进行完美升级,以及对其核心目…...
C/C++|经典代码题(动态资源的双重释放与「按值传递、按引用传递、智能指针的使用」)
以下代码中你能看出其存在什么问题?如何修复,能给出几种方法?分别在什么场景下用哪种方法。 #include <iostream>class Buffer {public:Buffer() { std::cout << "Buffer created" << std::endl; }~Buffer() { s…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
