[重学Python] Day8 面向对象编程:详解基于接口编程、组合优于继承、控制反转及SOLID五个原则

[重学Python] Day8 面向对象编程:详解基于接口编程、组合优于继承、控制反转及SOLID五个原则

    • 一、面向对象编程的理念
      • 1、基于接口编程
        • 面向对象中基于接口编程主要有以下一些优点和特点
      • 2、组合优于继承
        • 组合优于继承的优点和特点主要包括以下几点
      • 3、控制反转
        • 通过控制反转,我们可以实现以下好处
    • 二、面向对象编程的原则
      • 1、单一职责原则
      • 2、开闭原则
      • 3、里氏替换原则
      • 4、接口隔离原则
      • 5、依赖倒置原则
    • 三、面向对象编程的优缺点
      • 1、优点
      • 2、缺点

一、面向对象编程的理念

在面向对象编程中,基于接口的编程和组合优于继承是两个重要的概念。

在实际应用中,基于接口的编程和组合优于继承可以结合使用,以提高系统的灵活性和可扩展性。例如,可以定义一个接口来表示某个功能,然后通过实现该接口的不同类来提供不同的实现方式。在使用时,可以根据具体的需求选择合适的实现类进行组合,从而实现所需的功能。

1、基于接口编程

基于接口编程是一种面向对象编程的设计原则。它的主要思想是将具体的实现细节隐藏在接口后面,让使用者只关心接口的定义和功能,而不需要关心具体的实现方式。

想象一下,你要设计一个汽车租赁系统。在这个系统中,有各种不同类型的汽车,比如轿车、卡车、公交车等。这些汽车都有一些共同的功能,比如启动、加速、刹车、转弯等。

如果我们直接使用具体的汽车类型来编程,那么代码就会紧密地绑定到具体的汽车实现上。这样一来,如果我们要添加一种新类型的汽车,或者修改现有汽车的实现方式,就需要修改很多相关的代码,这会增加代码的复杂性和维护成本。

为了解决这个问题,我们可以使用接口来抽象出汽车的共同功能。接口就像是一个合同,它定义了汽车应该具备的功能,但并没有具体实现这些功能。不同类型的汽车可以实现这个接口,从而提供自己的实现方式。

比如,我们可以定义一个名为 ICar 的接口,它包含启动、加速、刹车、转弯等方法。然后,轿车、卡车、公交车等具体的汽车类型可以实现这个接口,提供自己的实现方式。

在代码中,我们只需要使用 ICar 接口来操作汽车,而不需要关心具体使用的是哪种类型的汽车。这样一来,我们的代码就可以更加灵活地应对变化。如果我们要添加一种新类型的汽车,只需要让它实现 ICar 接口,就可以在代码中使用了。

在下面的这段代码中:

  • 我们定义了一个名为Animal的接口类,它只有一个方法make_sound
  • Dog类和Cat类都继承自Animal类,并分别实现了make_sound方法,给出了不同的行为。
  • 在主程序中,我们创建了DogCat的实例,并调用它们的make_sound方法,展示了基于接口编程的灵活性和多态性。
# 定义接口
class Animal:def make_sound(self):pass# 实现接口的类
class Dog(Animal):def make_sound(self):print("汪汪汪")class Cat(Animal):def make_sound(self):print("喵喵喵")# 使用示例
dog = Dog()
cat = Cat()dog.make_sound()
cat.make_sound()
面向对象中基于接口编程主要有以下一些优点和特点
  1. 实现解耦:接口定义了一组行为规范,而具体的实现可以在不同的类中进行。这样,调用方只依赖于接口,而不直接依赖于具体的实现类,降低了系统各部分之间的耦合度。
  2. 提高灵活性:当需要更改实现时,只需要修改具体的实现类,而不会影响到使用接口的其他部分。
  3. 多态性支持:基于接口可以实现多态,使得不同的实现类可以在同一接口下被统一处理,增加了程序的灵活性和扩展性。
  4. 便于协作:开发团队可以根据接口进行分工,不同的开发人员负责实现不同的接口,提高开发效率。
  5. 易于测试:可以针对接口编写测试用例,而不必关心具体的实现细节,提高测试的准确性和效率。

2、组合优于继承

组合优于继承是一种面向对象编程的设计原则。它的主要思想是在实现类的功能时,优先使用组合而不是继承。

继承是面向对象编程中的一种重要机制,它允许一个类从另一个类继承属性和方法。然而,继承也有一些潜在的问题,比如类的层次结构可能变得过于复杂,导致代码难以维护和扩展。

相比之下,组合是将一个类的对象作为另一个类的成员来使用。通过组合,我们可以将类的功能分解成更小的、更易于管理的部分,并且可以更灵活地组合这些部分来创建新的类。

下面通过一个例子来解释组合优于继承的原理:

假设我们有一个形状类(Shape),它定义了一些通用的形状属性和方法,比如颜色、大小、绘制方法等。然后我们有一些具体的形状类,比如圆形类(Circle)、正方形类(Square)等,它们继承了形状类的属性和方法,并实现了自己的特定功能。

但是,如果我们需要创建一个新的形状类,比如三角形类(Triangle),并且三角形类的绘制方法与圆形类和正方形类的绘制方法不同,那么我们就需要在三角形类中重新实现绘制方法。这可能会导致代码重复,并且如果我们需要修改绘制方法的实现,就需要在多个类中进行修改。

为了解决这个问题,我们可以使用组合的方式来实现三角形类。具体来说,我们可以将三角形类定义为一个包含三个点的对象,并将绘制方法作为一个单独的类来实现。然后,三角形类可以将绘制类的对象作为成员来使用,并在需要绘制三角形时调用绘制类的方法。

通过使用组合,我们可以避免代码重复,并且可以更灵活地修改类的功能。例如,如果我们需要修改绘制方法的实现,只需要修改绘制类的代码,而不需要修改三角形类的代码。

# 形状类,定义了通用的形状属性和方法
class Shape:def __init__(self, color, size):self.color = colorself.size = sizedef draw(self):print("绘制形状")# 圆形类,继承自形状类
class Circle(Shape):def __init__(self, color, size, radius):super().__init__(color, size)self.radius = radiusdef draw(self):print("绘制圆形")# 正方形类,继承自形状类
class Square(Shape):def __init__(self, color, size, side_length):super().__init__(color, size)self.side_length = side_lengthdef draw(self):print("绘制正方形")# 三角形类,使用组合方式实现
class Triangle:def __init__(self, color, size, points):self.color = colorself.size = sizeself.points = pointsself.drawing_method = Drawing_methoddef draw(self):self.drawing_method.draw_triangle(self.points)# 定义绘制三角形的方法
class drawing_method:def draw_triangle(self, points):print("绘制三角形")# 创建圆形对象
circle = Circle("红色", 10, 5)# 创建正方形对象
square = Square("蓝色", 20, 10)# 创建三角形对象
triangle = Triangle("绿色", 30, [(0, 0), (10, 0), (10, 10)])# 调用对象的绘制方法
circle.draw()
square.draw()
triangle.draw()

在这个例子中,我们首先定义了一个形状类Shape,它具有颜色和大小属性以及一个通用的draw方法。然后,我们定义了圆形类Circle和正方形类Square,它们都继承自Shape类,并添加了自己特定的属性和方法。

对于三角形类Triangle,我们没有使用继承,而是使用组合的方式。Triangle类将一个drawing_method对象作为成员变量,并在draw方法中调用该对象的draw_triangle方法来绘制三角形。

通过这种方式,我们可以避免在Triangle类中重复实现绘制三角形的代码,并且可以更灵活地修改绘制三角形的方式,只需要修改drawing_method类的代码即可。

组合优于继承的优点和特点主要包括以下几点
  1. 代码复用:通过组合,可以将不同类的对象组合在一起,实现代码的复用。而继承则是在子类中重用父类的代码。
  2. 可维护性:组合使得代码更容易维护,因为可以在不影响其他类的情况下修改某个类的实现。而继承则可能导致代码的紧耦合,修改父类可能会影响到子类的行为。
  3. 灵活性:组合提供了更大的灵活性,可以根据需要动态地组合不同的对象来创建新的对象。而继承则受限于父类的定义。
  4. 易于扩展:使用组合可以更容易地添加新的功能或行为,只需创建新的对象并将其组合到现有对象中。而在继承中,扩展子类可能会受到父类的限制。
  5. 避免多继承的问题:多继承可能会导致复杂的继承层次结构和方法重写的问题。使用组合可以避免这些问题,因为每个对象只与它所组合的对象相关联。
  6. 提高可读性:组合的代码通常更易于理解,因为可以更清晰地看到各个对象之间的关系和交互。

3、控制反转

在面向对象编程中,控制反转(Inversion of Control,缩写为 IoC)是一种设计模式,它将对象的创建和依赖关系的管理控制权从代码中转移到外部容器或框架中。

传统的编程方式通常是在代码中直接创建对象,并通过硬编码的方式来管理对象之间的依赖关系。这种方式使得代码中的对象紧密地耦合在一起,不利于代码的维护和扩展。

而控制反转则采用了一种相反的方式。它将对象的创建和依赖关系的管理交给了外部的容器或框架。在运行时,容器或框架会根据配置信息或规则,自动创建对象并注入它们之间的依赖关系。

# 定义一个汽车类,它需要一个发动机对象作为依赖
class Car:def __init__(self, engine):self.engine = enginedef drive(self):self.engine.start()# 定义一个发动机类
class Engine:def start(self):print("发动机启动")# 使用控制反转来创建汽车对象
# 创建一个容器,负责创建和管理对象之间的依赖关系
container = Container()# 向容器中注册发动机对象的创建逻辑
container.register_engine(lambda: Engine())# 从容器中获取汽车对象,并注入发动机依赖
car = container.get_car()# 调用汽车对象的驾驶方法
car.drive()

在上述示例中,我们定义了一个汽车类和一个发动机类。汽车类需要一个发动机对象作为依赖。但是,我们没有在汽车类的代码中直接创建发动机对象,而是使用了一个容器来管理对象之间的依赖关系。

容器通过注册引擎对象的创建逻辑,负责在运行时创建发动机对象,并将其注入到汽车对象中。这样,汽车对象就可以在不关心发动机对象具体创建细节的情况下使用它。

控制反转是一种非常重要的设计模式,它在现代面向对象编程中被广泛应用于各种框架和架构中,如 Spring、Django 等。

通过控制反转,我们可以实现以下好处
  1. 降低耦合度:代码中的对象不再直接依赖于其他对象的创建和管理,而是通过接口与外部容器或框架进行交互。这样可以减少对象之间的耦合,提高代码的可维护性和可扩展性。
  2. 提高代码的复用性:由于对象的创建和依赖关系的管理由外部容器或框架负责,我们可以更容易地在不同的代码模块中复用对象,而无需关心它们的具体创建和管理细节。
  3. 便于测试:在使用控制反转的情况下,我们可以通过模拟外部容器或框架来创建和管理对象,从而更容易进行单元测试和集成测试。
  4. 支持动态配置:外部容器或框架可以根据配置信息或运行时环境的变化,动态地调整对象的创建和依赖关系。这样可以提高代码的灵活性和适应性。

控制反转通常通过依赖注入(Dependency Injection)的方式来实现。依赖注入是指将对象所需的依赖关系在运行时通过构造函数、属性或方法注入到对象中。

二、面向对象编程的原则

面向对象编程有五个基本原则,也被称为 SOLID 原则。这五个原则分别是:

1、单一职责原则

单一职责原则(Single Responsibility Principle,简称 SRP):一个类应该只有一个引起它变化的原因。

它的核心思想是一个类或模块应该只负责一个功能或职责,并且这个功能或职责应该是完整且独立的。

换句话说,每个类或模块应该只有一个原因导致它的修改。如果一个类承担了多个职责,当其中一个职责发生变化时,可能会影响到其他职责的正常工作,从而增加了代码的复杂性和维护难度。

以下是一个示例来帮助你理解单一职责原则:

假设我们有一个 Order 类,它包含了订单的详细信息,如客户信息、订单条目、订单状态等。这样的设计可能会导致问题,因为 Order 类承担了太多的职责。

更好的做法是将 Order 类分解为多个专门的类,例如 CustomerInfo 类负责客户信息,OrderItems 类负责订单条目,OrderStatus 类负责订单状态。这样,每个类都只负责一个特定的职责,并且更容易进行修改和扩展。

2、开闭原则

开闭原则(Open-Closed Principle,简称 OCP):软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。

它的主要思想是软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

这意味着我们应该尽量在不修改现有代码的情况下,通过扩展来增加新的功能或修改现有功能。换句话说,我们应该让代码具有良好的扩展性,以便在未来需要时能够轻松地添加新的功能或进行修改。

下面是一个简单的例子来解释开闭原则:

假设我们有一个计算加法的函数 calculate_sum(),它接受两个参数并返回它们的和。

def calculate_sum(num1, num2):return num1 + num2

现在我们需要添加一个新的功能,计算三个数的和。如果我们直接修改 calculate_sum() 函数来处理三个参数,那么就违反了开闭原则,因为我们对现有的代码进行了修改。

更好的做法是创建一个新的函数 calculate_sum_three_numbers() 来处理三个数的和,同时保持 calculate_sum() 函数不变。

def calculate_sum(num1, num2):return num1 + num2def calculate_sum_three_numbers(num1, num2, num3):return num1 + num2 + num3

这样,我们通过扩展新的函数来实现了新的功能,而没有修改现有的代码。这符合开闭原则,因为我们对代码进行了扩展,而没有关闭它的修改。

3、里氏替换原则

里氏替换原则(Liskov Substitution Principle,简称 LSP):所有引用基类的地方必须能透明地使用其子类的对象。

它的主要思想是在一个软件系统中,如果子类能够替换父类,并且不会导致系统出现异常或错误,那么这个子类就是符合里氏替换原则的。

简单来说,就是子类应该能够在父类的位置上正常工作,而不会改变系统的行为。也就是说,子类应该继承父类的所有行为,并且不会添加或删除父类的行为。

下面是一个简单的例子来解释里氏替换原则:

假设我们有一个父类 Animal,它有一个方法 move(),用于表示动物的移动行为。

class Animal:def move(self):print("动物在移动...")

然后,我们有一个子类 Dog,它继承自父类 Animal,并添加了自己的行为。

class Dog(Animal):def bark(self):print("小狗在叫...")

在这个例子中,子类 Dog 继承了父类 Animalmove() 方法,并添加了自己的 bark() 方法。这是符合里氏替换原则的,因为子类 Dog 可以在任何需要父类 Animal 的地方使用,并且不会改变系统的行为。

但是,如果我们在子类 Dog 中重写了父类 Animalmove() 方法,并且改变了它的行为,那么就违反了里氏替换原则。

class Dog(Animal):def move(self):print("小狗在跳跃...")

在这个例子中,子类 Dog 重写了父类 Animalmove() 方法,并将其行为改为了跳跃。如果我们在一个需要父类 Animal 的地方使用子类 Dog,那么系统的行为就会发生改变,这就违反了里氏替换原则。

4、接口隔离原则

接口隔离原则(Interface Segregation Principle,简称 ISP):使用多个专门的接口,而不使用单一的总接口。

它的核心思想是不应该强迫客户端依赖于它们不需要的接口。

换句话说,一个接口应该只提供客户端真正需要的方法,而不应该包含客户端不需要的方法。

以下是一个示例来帮助你理解接口隔离原则:

假设我们有一个接口 IUserService,它包含了所有与用户服务相关的方法,如 create_user()update_user()delete_user()query_user()

现在有两个客户端类,一个是 AdminPanel,它只需要使用 create_user()update_user() 方法;另一个是 UserDashboard,它只需要使用 query_user() 方法。

如果 AdminPanelUserDashboard 都直接依赖于 IUserService 接口,那么它们就会被迫实现不需要的方法,这违反了接口隔离原则。

更好的做法是将 IUserService 接口分解为两个更小的接口,一个是 IAdminUserService,只包含 create_user()update_user() 方法;另一个是 IUserUserService,只包含 query_user() 方法。

这样,AdminPanel 类就可以只依赖于 IAdminUserService 接口,而 UserDashboard 类就可以只依赖于 IUserUserService 接口,从而实现了接口的隔离。

5、依赖倒置原则

依赖倒置原则(Dependency Inversion Principle,简称 DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

它的主要思想是高层模块不应该依赖于底层模块,而应该依赖于抽象。抽象不应该依赖于细节,而细节应该依赖于抽象。

简单来说,就是将依赖关系反转,让高层模块依赖于抽象,而不是具体的实现细节。这样可以提高代码的灵活性和可维护性。

下面是一个简单的例子来解释依赖倒置原则:

假设我们有一个文件读取模块 FileReader 和一个文件写入模块 FileWriter

class FileReader:def read_file(self, file_path):# 读取文件的具体实现class FileWriter:def write_file(self, file_path, content):# 写入文件的具体实现# 客户端代码
def process_file(file_path, content):reader = FileReader()writer = FileWriter()file_data = reader.read_file(file_path)writer.write_file(file_path, file_data)if __name__ == '__main__':process_file('file.txt', 'Hello, World!')

在这个例子中,客户端代码直接依赖于具体的 FileReaderFileWriter 类。这违反了依赖倒置原则,因为客户端代码依赖于具体的实现细节,而不是抽象。

更好的做法是将文件读取和写入的功能抽象成一个接口 IFileReaderIFileWriter

class IFileReader:def read_file(self, file_path):passclass IFileWriter:def write_file(self, file_path, content):passclass FileReaderImpl(IFileReader):def read_file(self, file_path):# 读取文件的具体实现class FileWriterImpl(IFileWriter):def write_file(self, file_path, content):# 写入文件的具体实现# 客户端代码
def process_file(file_path, content):reader = FileReaderImpl()writer = FileWriterImpl()file_data = reader.read_file(file_path)writer.write_file(file_path, file_data)if __name__ == '__main__':process_file('file.txt', 'Hello, World!')

在这个例子中,客户端代码依赖于抽象的 IFileReaderIFileWriter 接口。具体的实现细节通过子类 FileReaderImplFileWriterImpl 来完成。这样,如果需要更改文件读取或写入的方式,只需要修改子类的实现,而不需要修改客户端代码,从而提高了代码的灵活性和可维护性。

这些原则有助于提高代码的可维护性、可读性和可扩展性。在实际开发中,遵循这些原则可以使代码更易于理解、修改和扩展,从而提高软件的质量和开发效率。

需要注意的是,SOLID 原则并不是绝对的,在某些情况下可能需要权衡和灵活应用。但总体来说,它们是面向对象编程中非常重要的指导原则,可以帮助我们编写更好的代码。

三、面向对象编程的优缺点

1、优点

  1. 封装和信息隐藏:将数据和方法封装在对象中,提高了代码的可读性和可维护性,同时保护了内部数据的安全性。
  2. 代码复用:通过继承和多态,可以实现代码的复用,减少重复代码的编写。
  3. 提高代码的可读性和可维护性:面向对象编程使代码更具结构化,更容易理解和维护。
  4. 模拟现实世界:面向对象编程可以更好地模拟现实世界中的对象和关系,使代码更具可读性和可理解性。
  5. 提高代码的可扩展性:可以方便地添加新的类和方法来扩展代码的功能。
  6. 提高代码的健壮性:面向对象编程可以通过封装和继承来提高代码的健壮性,减少错误的发生。

2、缺点

  1. 性能损失:面向对象编程可能会导致一定的性能损失,特别是在处理大量数据时。
  2. 代码复杂度增加:面向对象编程可能会导致代码复杂度增加,特别是在处理复杂的对象关系时。
  3. 学习曲线较高:面向对象编程需要一定的学习成本,需要掌握类、对象、继承、多态等概念。
  4. 过度设计:在面向对象编程中,可能会出现过度设计的情况,导致代码过于复杂。

需要注意的是,面向对象编程的优缺点并不是绝对的,具体情况取决于具体的应用场景和需求。在实际开发中,需要根据实际情况权衡利弊,选择最适合的编程方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/832048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Day13-JavaWeb开发-事务管理(回顾/进阶)AOP基础(入门/概念)AOP进阶(通知类型/顺序/切点表达式)AOP案例

1. 事务管理 1.1 事务管理-事务回顾 1.2 事务管理-事务进阶 rollbackFor propagetion 2. AOP基础 2.1 AOP基础-快速入门 2.2 AOP基础-核心概念 3. AOP进阶 3.1 AOP进阶-通知类型 3.2 AOP进阶-通知顺序 3.3 切入点表达式-execution 3.4 切入点表达式-annottation 3.5 AOP进阶…

如何构建用于从收据中提取信息的生成式人工智能工具

原文地址:how-to-build-a-generative-ai-tool-for-information-extraction-from-receipts 使用 LangChain 和 OpenAI 工具从 Google Drive 中存储的收据图像中提取结构化信息 2024 年 4 月 10 日 纸质收据有各种样式和格式,是自动信息提取的一个有趣目…

Web服务器手动配置

目录 配置环境 http配置 配置步骤 1、首先安装Nginx(已经安装的跳过这步) 2、查看一下下Nginx的配置文件结构,了解如何配置,以及配置的各个条目有什么作用(为接下来的配置打基础) 3、创建你的网页 4、…

【JAVA项目】基于ssm的协同过滤算法的【图书推荐系统】

技术简介:采用B/S架构、ssm 框架、Java技术、MySQL等技术实现。 系统简介:系统权限按管理员和用户这两类涉及用户划分。(1)管理员功能需求 管理员登陆后,主要包括首页、个人中心、用户管理、书籍管理、书籍分类管理、热…

牛客NC382 切割木头【中等 二分超找 Java/Go/C++】

题目 题目链接: https://www.nowcoder.com/practice/707d98cee255448c838c76918a702be0 核心 二分查找Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可…

安卓 app icon大小 安卓app界面尺寸大小

移动应用的界面设计画布尺寸设计多大(特别是Android)、图标和字体大小怎么定、需要设计多套设计稿么、如何切图以配合开发的实现? 本篇将结合iOS和android官方的设计规范、搜集的资料以及工作中的摸索,来分享移动应用界面设计中的…

gcc编译器分析

gcc编译器分析 参考词法分析语法分析预读一个符号语法分析函数调用关系重点函数分析c_parse_filec_parser_translation_unit 参考 《gcc源码分析》 词法分析 词法分析的过程就是将源代码识别成一个一个的词法符号,并在词法分析的过程中创建一些树节点&#xff0c…

tomcat+nginx 动静分离

一、单机反向代理 7-1 7-2 测试 二、多机反向代理 1. 环境准备 机器IP地址服务7-1172.168.1.11nginx7-2172.168.1.12tomcat7-3172.168.1.13tomcat 2. 配置7-1 Nginx 服务器 vim /apps/nginx/conf/nginx.confhttp:upstream tomcat {server 172.168.1.12:8080;server …

探索设计模式的魅力:分布式模式让业务更高效、更安全、更稳定

​🌈 个人主页:danci_ 🔥 系列专栏:《设计模式》 💪🏻 制定明确可量化的目标,坚持默默的做事。 ✨欢迎加入探索分布式模式之旅✨ 在数字化时代,企业面临着前所未有的挑战和机遇。…

STM32开发笔记-新建标准库工程

1.STM32开发方式 STM32开发一般包括三种方式:基于寄存器开发、基于标准外设库开发、基于HAL库开发。 标准外设库是最基础的STM32开发方式,提供了一系列函数用于配置和控制STM32的外设,如GPIO、USART、SPI等。使用标准外设库需要手动编写代码…

(39)4.29数据结构(栈,队列和数组)栈

#include<stdlib.h> #include<stdio.h> #define MaxSize 10 #define Elemtype int 1.栈的基本概念 2.栈的基本操作 typedef struct { Elemtype data[MaxSize]; int top; }Sqstack;//初始化栈 void InitStack(Sqstack& S) { S.top -1; //初始化…

Golang日志管理:使用log/slog实现高级功能和性能优化

Golang日志管理&#xff1a;使用log/slog实现高级功能和性能优化 简介基础使用初始化和配置日志级别 高级技巧自定义日志格式器条件日志处理 实战案例场景一&#xff1a;API请求日志记录场景二&#xff1a;错误跟踪和用户通知 性能优化优化日志记录的性能异步日志处理选择合适的…

Vue的项目启动指令分析

通过Vue CLI脚手架创建的项目&#xff0c;默认的启动项目方式是 npm run serve 这里的serve是可以修改的。 在创建的项目目录中&#xff0c;找到package.json 双击打开&#xff0c;找到scripts部分 在scripts部分&#xff0c;有一个"serve"键值对&#xff0c;这里的…

机器学习-K近邻算法(KNN)

目录 什么是KNN算法 图解KNN基本算法 &#xff08;1&#xff09;k近邻算法中k的选取 &#xff08;2&#xff09;距离函数 &#xff08;3&#xff09;归一化处理 &#xff08;4&#xff09;概率kNN KNN算法的优缺点 优势 缺点 KNN算法总结 什么是KNN算法 k近邻算法&…

[Spring Cloud] (6)gateway整体加解密

文章目录 简述整体效果后端增加配置nacos增加配置GlobalConfig 添加请求整体解密拦截器DecryptionFilter添加响应整体解密拦截器EncryptionFilter 前端请求拦截器添加整体加密逻辑请求头中添加sessionId 响应拦截器添加整体解密逻辑 简述 本文网关gateway&#xff0c;微服务&a…

[C语言]指针进阶详解

指针是C语言的精髓所以内容可能会比较多&#xff0c;需要我们认真学习 目录 1、字符指针 2、指针数组 3、数组指针 3.1数组指针的定义 3.2&数组名vs数组名 3.3数组指针的使用 4、数组传参和指针传参 4.1一维数组传参 4.2二维数组传参 4.3一级指针传参 4.4二级指…

学习如何使用PyQt5实现notebook功能

百度搜索“pyqt5中notebook控件”&#xff0c;AI自动生成相应例子的代码。在 PyQt5 中&#xff0c;QTabWidget 类被用作 Notebook 控件。以下是一个简单的示例&#xff0c;展示如何创建一个带有两个标签的 Notebook 控件&#xff0c;并在每个标签中放置一些文本。 import sys f…

45. UE5 RPG 增加角色受击反馈

在前面的文章中&#xff0c;我们实现了对敌人的属性的初始化&#xff0c;现在敌人也拥有的自己的属性值&#xff0c;技能击中敌人后&#xff0c;也能够实现血量的减少。 现在还需要的就是在技能击中敌人后&#xff0c;需要敌人进行一些击中反馈&#xff0c;比如敌人被技能击中后…

使用macof发起MAC地址泛洪攻击

使用macof发起MAC地址泛洪攻击 MAC地址泛洪攻击原理&#xff1a; MAC地址泛洪攻击是一种针对交换机的攻击方式&#xff0c;目的是监听同一局域网中用户的通信数据。交换机的工作核心&#xff1a;端口- MAC地址映射表。这张表记录了交换机每个端口和与之相连的主机MAC地址之间…

Spring Boot与JSP的浪漫邂逅:轻松构建动态Web应用的秘诀

本文介绍 Spring Boot 集成 JSP。 1、pom.xml 增加对 JSP 的支持 Spring Boot 的默认视图支持是 Thymeleaf 模板引擎&#xff0c;如果想要使用 JSP 页面&#xff0c;需要配置 servlet 依赖和 tomcat 的支持。 在 pom.xml 文件中增加如下代码&#xff1a; <!-- servlet依赖 -…