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

Python面向对象编程:探索代码的结构之美

在这里插入图片描述

文章目录

    • 一、引言
    • 二、为什么学习面向对象编程
      • 2.1 提高代码的可维护性:通过封装、继承和多态实现模块化设计
      • 2.2 提升代码的复用性:通过类和对象的创建实现代码的重用
    • 三、类和对象的基本概念
      • 3.1 类和对象的定义和关系:类是对象的模板,对象是类的实例
      • 3.2 属性和方法:类和对象具有的特征和行为
      • 3.3 初始化方法:__init__()方法的作用和使用
    • 四、类的继承与多态
      • 4.1 继承的概念和分类:单继承和多继承
      • 4.2 多态的实现:不同对象对相同方法的不同表现
    • 五、封装和访问控制
      • 5.1 封装的概念和作用:将数据和操作封装在类中
      • 5.2 公有、私有和受保护的访问控制:属性和方法的可见性和访问限制
    • 六、魔法方法和特殊属性
      • 6.1 魔法方法的介绍和使用:__repr__()、__str__()等
      • 6.2 特殊属性的应用:__dict__、__name__等
    • 七、类的设计原则和模式
      • 7.1 SOLID原则:单一职责、开闭原则、里式替换、接口隔离原则
      • 7.2 常见设计模式:工厂模式、单例模式、观察者模式等
    • 八、实践项目:创建一个简单的图书管理系统

重温Python,适合新手搭建知识体系,也适合大佬的温故知新~

一、引言

面向对象编程为Python开发提供了一种有效的编程范式,可以提高代码的可重用性、简化复杂性、增强扩展性、保护数据安全和提高代码的可维护性。

面向对象编程对Python的重要性

  1. 可重用性:面向对象编程通过封装数据和行为在一个对象中,使得代码更加模块化和可重用。通过定义类和创建对象,可以在不同的项目中重复使用代码,提高开发效率。
  2. 简化复杂性:面向对象编程通过抽象、继承和多态等特性,可以将复杂的问题分解成简单的对象,并通过对象之间的交互解决问题。这样可以简化代码的设计和实现过程,提高代码的可读性和维护性。
  3. 扩展性:面向对象编程允许通过继承和多态来扩展已有的类,从而实现新的功能。这种灵活性使得代码可以随着需求的变化而进行扩展和修改,同时保持原有代码的稳定性。
  4. 封装性:面向对象编程通过将数据和行为封装在一个对象中,实现了数据的隐藏和保护。这样可以防止外部直接访问和修改对象的内部状态,提高代码的安全性和稳定性。
  5. 可维护性:面向对象编程使得代码更加结构化和模块化,易于理解和维护。通过将功能相似的代码封装在类中,可以更好地组织代码,减少了代码的冗余和重复,使得代码更加可读和易于修改。

二、为什么学习面向对象编程

2.1 提高代码的可维护性:通过封装、继承和多态实现模块化设计

通过封装、继承和多态的应用,可以将代码模块化,降低代码的耦合度,增加代码的可读性、可维护性和可扩展性 ,可以提高代码的可维护性,并实现模块化的设计。

封装

封装是指将数据和对数据的操作封装在一起,以形成一个独立的单元。通过封装,我们可以隐藏内部实现细节,只暴露必要的接口给其他部分使用。这样可以增强代码的安全性和可维护性。

class Person:def __init__(self, name, age):self.name = nameself.age = agedef get_name(self):return self.namedef get_age(self):return self.ageperson = Person("Alice", 25)print(person.get_name())  # 输出 "Alice"
print(person.get_age())  # 输出 25

继承

继承是指通过创建一个新类,从现有的类中继承属性和方法。通过继承,可以避免重复编写代码,提高代码的重用性。同时,通过继承还可以实现多层次的抽象和模块化的设计。

class Animal:def __init__(self, name):self.name = namedef speak(self):passclass Dog(Animal):def speak(self):return "Woof!"class Cat(Animal):def speak(self):return "Meow!"dog = Dog("Buddy")
cat = Cat("Whiskers")print(dog.speak())  # 输出 "Woof!"
print(cat.speak())  # 输出 "Meow!"

多态

多态是指相同的接口可以有不同的实现方式。通过多态,可以将不同的对象当作同一类型来对待,从而增加代码的灵活性和可扩展性。

class Shape:def area(self):passclass Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self):return 3.14 * self.radius * self.radius# 将Rectangle和Circle对象当作Shape类型的对象来对待,并调用相同的方法area,从而实现了对不同对象的统一处理
shapes = [Rectangle(4, 5), Circle(3)]for shape in shapes:print(shape.area())

2.2 提升代码的复用性:通过类和对象的创建实现代码的重用

通过类和对象的创建,可以实现代码的重用和模块化设计。将相关的属性和方法封装在一个类中,可以提高代码的可读性和可维护性;通过对象的创建,可以实现对类中属性和方法的共享和调用;通过继承的应用,可以实现代码的重用和扩展,避免了重复编写相同功能的代码。

小案例

假设我们有一个表示汽车的类Car,其中包含了汽车的颜色、品牌和型号等属性,以及加速、刹车和转向等方法。我们可以创建多个Car对象,每个对象都具有相同的属性和方法,但是它们的具体属性值可能不同。

class Car:def __init__(self, color, brand, model):self.color = colorself.brand = brandself.model = model# 加速def accelerate(self):print(f"The {self.color} {self.brand} {self.model} is accelerating.")# 刹车def brake(self):print(f"The {self.color} {self.brand} {self.model} is braking.")# 转向def turn(self, direction):print(f"The {self.color} {self.brand} {self.model} is turning {direction}.")# 创建两个Car对象,具有相同的属性和方法,但具体的属性值不同
car1 = Car("red", "Toyota", "Camry")
car2 = Car("blue", "Honda", "Civic")# 调用对象的方法
car1.accelerate()  # 输出 "The red Toyota Camry is accelerating."
car2.brake()  # 输出 "The blue Honda Civic is braking."

代码重用性的好处:如果我们需要创建更多的汽车对象,只需简单地实例化新的对象即可,而无需重新编写类或方法。此外,如果我们需要修改汽车的某个属性或方法,只需在类定义中进行修改,所有的对象都会受到影响。

三、类和对象的基本概念

3.1 类和对象的定义和关系:类是对象的模板,对象是类的实例

类和对象之间的关系

  • 类是对象的模板或蓝图,它定义了对象应该具有的属性和方法。
  • 对象是根据类的定义创建的一个具体实体,它具有类定义中所描述的属性和方法。

类和对象之间的关系

class Person:def __init__(self, name, age):self.name = nameself.age = agedef say_hello(self):print(f"Hello, my name is {self.name} and I am {self.age} years old.")# 创建Person类的对象
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)# 访问对象的属性和方法
print(person1.name)  # 输出 "Alice"
print(person2.age)  # 输出 30person1.say_hello()  # 输出 "Hello, my name is Alice and I am 25 years old."
person2.say_hello()  # 输出 "Hello, my name is Bob and I am 30 years old."

3.2 属性和方法:类和对象具有的特征和行为

属性 | 方法和类 | 对象之间的关系

  • 属性是类或对象的特征或状态,用于描述对象的状态、特征或数据。
  • 方法是类或对象的行为或操作,用于执行某些具体的动作或操作。

属性和方法在类和对象中的应用

class Dog:def __init__(self, name, age):self.name = nameself.age = agedef bark(self):print(f"{self.name} is barking!")def eat(self, food):print(f"{self.name} is eating {food}.")# 创建Dog类的对象
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)# 访问对象的属性
print(dog1.name)  # 输出 "Buddy"
print(dog2.age)  # 输出 5# 调用对象的方法
dog1.bark()  # 输出 "Buddy is barking!"
dog2.eat("bone")  # 输出 "Max is eating bone."

3.3 初始化方法:init()方法的作用和使用

init()方法用于在创建对象时对对象进行初始化。它的作用是在新建对象时分配内存空间,并对对象的属性进行初始化,通常被放在类的定义中,并且必须以self作为第一个参数。它可以接受任意数量的额外参数,用于初始化对象中的属性。

简单的Person类的定义

class Person:def __init__(self, name, age):self.name = nameself.age = age

当创建一个Person对象时,必须提供name和age参数,Python会自动调用__init__()方法来创建person1对象并进行初始化。

创建了一个名为person1的Person对象

person1 = Person("Alice", 25)

init()方法的作用是确保对象在创建时处于正确的状态,并且可以避免在后续操作中出现错误。通过使用__init__()方法,可以方便地对对象的属性进行初始化,并且可以在创建对象时自动完成这些操作,从而提高了代码的可读性和可维护性。

四、类的继承与多态

4.1 继承的概念和分类:单继承和多继承

继承是面向对象编程中的重要概念,它允许我们构建具有层次结构的类,并且可以在子类中重用和扩展父类的功能。单继承和多继承提供了不同的继承方式,可以根据具体需求选择适合的继承方式。通过继承,一个类可以派生出子类,从而使子类可以继承父类的属性和方法。这种机制允许我们创建具有层次结构的类,其中子类可以继承和扩展父类的功能。

  1. 单继承

单继承是指一个子类只能继承一个父类的特性。在类定义时将父类作为参数传递给子类,实现单继承。子类会继承父类的所有属性和方法,并且可以在子类中添加新的属性和方法。

# 父类
class Parent:def __init__(self):self.parent_attr = "Parent attribute"def parent_method(self):print("Parent method")# 子类
# 通过调用super().init()方法,子类可以调用父类的初始化方法来初始化父类的属性
class Child(Parent):def __init__(self):super().__init__()  # 调用父类的初始化方法self.child_attr = "Child attribute"def child_method(self):print("Child method")

创建一个Child对象并调用父类和子类的方法

obj = Child()
obj.parent_method()  # 输出 "Parent method"
obj.child_method()  # 输出 "Child method"
print(obj.parent_attr)  # 输出 "Parent attribute"
print(obj.child_attr)  # 输出 "Child attribute"
  1. 多继承

多继承是指一个子类可以同时继承多个父类的特性。在类定义时将多个父类作为参数传递给子类,实现多继承。

# 父类
class Parent1:def parent1_method(self):print("Parent1 method")# 父类
class Parent2:def parent2_method(self):print("Parent2 method")# 子类
class Child(Parent1, Parent2):def child_method(self):print("Child method")

创建一个Child对象并调用父类和子类的方法

obj = Child()
obj.parent1_method()  # 输出 "Parent1 method"
obj.parent2_method()  # 输出 "Parent2 method"
obj.child_method()  # 输出 "Child method"

ps:在多继承中,如果多个父类中有相同的方法,子类会按照继承的顺序优先调用第一个父类的方法。这种潜在的冲突可以通过方法重写或使用super()来解决。

4.2 多态的实现:不同对象对相同方法的不同表现

多态是指不同对象对相同方法的不同表现。具体来说,多态允许我们在不考虑对象类型的情况下,调用同一个方法,不同的对象会对该方法做出不同的响应。

多态的实现

class Animal:def __init__(self, name):self.name = name# 方法没有任何实现,因为不同的动物会发出不同的声音def make_sound(self):pass# 子类Dog和Cat重写了make_sound()方法,返回它们各自的声音
class Dog(Animal):def make_sound(self):return "Woof"class Cat(Animal):def make_sound(self):return "Meow"

通过这种方式,我们可以创建一个Animal对象,并调用make_sound()方法,不同的对象会对该方法做出不同的响应

# 创建一个Animal对象
animal = Animal("Generic animal")
# 创建一个Dog对象
dog = Dog("Fido")
# 创建一个Cat对象
cat = Cat("Whiskers")# 调用make_sound()方法
print(animal.make_sound())  # 输出 None
print(dog.make_sound())  # 输出 "Woof"
print(cat.make_sound())  # 输出 "Meow"

在不考虑对象类型的情况下,我们可以调用相同的方法,并让不同的对象对该方法做出不同的响应。这种机制使得代码更加灵活和可扩展,同时也避免了冗长的判断语句和复杂的类型转换。

五、封装和访问控制

5.1 封装的概念和作用:将数据和操作封装在类中

封装是指将数据和操作封装在类中,以达到数据的保护和隐藏实现细节的目的。具体来说,封装可以通过以下两种方式实现

  1. 将属性和方法设为私有的,即在属性和方法名称前面加上双下划线__。这样做可以防止属性被外部直接访问,同时也可以避免子类意外地修改父类的属性。如果需要访问或修改属性,可以通过gettersetter方法实现。
  2. 使用@property装饰器来创建gettersetter方法,使得我们可以像访问属性一样访问方法,从而隐藏实现细节。

封装的概念和作用

class Person:def __init__(self, name, age):self.__name = name  # 将属性设为私有的self.__age = age# 定义getter方法@propertydef name(self):return self.__name# 定义setter方法@name.setterdef name(self, value):self.__name = value# 定义getter方法@propertydef age(self):return self.__age# 定义setter方法,可以加入了一些判断逻辑来确保数据的合法性@age.setterdef age(self, value):if value < 0:# 传入的值不符合要求,则抛出ValueError异常raise ValueError("Age cannot be negative")self.__age = valuedef display_info(self):print(f"Name: {self.__name}, Age: {self.__age}")

通过创建Person对象,并调用它们的方法,我们可以看到封装的效果

# 创建一个Person对象
p = Person("Alice", 25)# 通过getter方法获取属性的值
print(p.name)  # 输出 "Alice"
print(p.age)  # 输出 25# 通过setter方法修改属性的值
p.name = "Bob"
p.age = 30p.display_info()  # 输出 "Name: Bob, Age: 30"

通过将数据和操作封装在类中,我们可以隐藏实现细节,保护数据的安全性,同时也提高了代码的可维护性和可扩展性。

5.2 公有、私有和受保护的访问控制:属性和方法的可见性和访问限制

属性和方法的可见性和访问限制是通过命名规则来实现的。

Python使用单下划线双下划线作为前缀来表示不同级别的访问控制

  1. 公有访问控制:没有任何前缀。公有属性和方法可以被类的实例、子类和其他代码直接访问。
  2. 私有访问控制:双下划线__作为前缀。私有属性和方法只能在类内部访问,无法通过实例、子类或其他代码直接访问。但是,在Python中,私有属性和方法并不是真正无法访问的,而是进行了名称修饰,将其名称变更为_类名__属性名_类名__方法名的形式,以实现一定程度的隐藏。
  3. 受保护的访问控制:单下划线_作为前缀。受保护属性和方法可以被类的实例和子类访问,但约定上不建议直接访问。这种访问控制主要是一种约定,用于告诉其他开发者某个属性或方法被认为是类内部使用的,不建议在外部直接使用。

公有、私有和受保护的访问控制

class MyClass:def __init__(self):self.public_var = "Public"  # 公有属性self._protected_var = "Protected"  # 受保护属性self.__private_var = "Private"  # 私有属性def public_method(self):print("This is a public method")  # 公有方法def _protected_method(self):print("This is a protected method")  # 受保护方法def __private_method(self):print("This is a private method")  # 私有方法# 创建一个类的实例
obj = MyClass()# 访问公有属性和调用公有方法
print(obj.public_var)  # 输出 "Public"
obj.public_method()  # 输出 "This is a public method"# 访问受保护属性和调用受保护方法
print(obj._protected_var)  # 输出 "Protected"
obj._protected_method()  # 输出 "This is a protected method"# 访问私有属性和调用私有方法(通过名称修饰)
print(obj._MyClass__private_var)  # 输出 "Private"
obj._MyClass__private_method()  # 输出 "This is a private method"

总结起来,公有访问控制允许外部直接访问属性和方法,私有访问控制将属性和方法隐藏在类内部,受保护访问控制则提供了一种约定,告诉其他开发者某个属性或方法是类内部使用的。这些访问控制级别可以帮助我们封装数据和隐藏实现细节,以提高代码的可维护性和安全性。

六、魔法方法和特殊属性

6.1 魔法方法的介绍和使用:repr()、str()等

**魔法方法(Magic Method)**是一些特殊的方法,它们以双下划线 __ 开头和结尾,例如 __repr__()__str__()。这些方法在某些特定场景下会被自动调用,可以用来实现类的一些特殊行为。

__repr__():

__repr__() 是一个魔法方法,用于返回一个对象的字符串表示形式(通常是一段可执行的代码),以便在交互式环境中显示或调试时使用。如果没有定义 __repr__() 方法,则调用 print() 函数打印该对象时,默认输出的是该对象的内存地址。

class Person:def __init__(self, name, age):self.name = nameself.age = agedef __repr__(self):return f"Person('{self.name}', {self.age})"p = Person("Alice", 25)
print(p)  # 输出 "Person('Alice', 25)"

__str__():

__str__() 是一个魔法方法,用于返回一个对象的字符串表示形式(通常是人类可读的文本),以便在用户界面或日志文件中使用。如果没有定义 __str__() 方法,则调用 print() 函数打印该对象时,会尝试调用 __repr__() 方法来获取字符串表示。

class Person:def __init__(self, name, age):self.name = nameself.age = agedef __repr__(self):return f"Person('{self.name}', {self.age})"def __str__(self):return f"{self.name}, {self.age}"# 输出的是 "Alice, 25",而不是默认的内存地址或__repr__()方法返回的字符串
p = Person("Alice", 25)
print(p)  # 输出 "Alice, 25"

6.2 特殊属性的应用:dict、__name__等

**特殊属性(Special Attribute)**是一些预定义的属性,以双下划线 __ 开头和结尾,例如 __dict____name__ 等。这些属性提供了有关对象或类的一些额外信息,可以在编程中使用。

__dict__:

__dict__ 是一个特殊属性,它是一个字典,包含了对象或类的所有属性和方法的名称和值。通过访问 __dict__ 属性,我们可以动态获取对象或类的属性和方法,并对其进行操作或修改,可以根据需要修改、添加或删除对象的属性。

class Person:def __init__(self, name, age):self.name = nameself.age = agedef greet(self):print(f"Hello, my name is {self.name}.")p = Person("Alice", 25)# 访问对象的 __dict__ 属性,获取到该对象的属性和对应的值的字典形式
print(p.__dict__)
# 输出: {'name': 'Alice', 'age': 25}# 修改对象的属性
p.age = 30
print(p.__dict__)
# 输出: {'name': 'Alice', 'age': 30}# 添加新的属性
p.location = "New York"
print(p.__dict__)
# 输出: {'name': 'Alice', 'age': 30, 'location': 'New York'}

__name__

__name__ 是一个特殊属性,它用于获取类或函数的名称。对于类而言,__name__ 返回类的名称;对于函数而言,__name__ 返回函数的名称。

class Person:passdef say_hello():print("Hello!")print(Person.__name__)  # 输出 "Person"
print(say_hello.__name__)  # 输出 "say_hello"

七、类的设计原则和模式

7.1 SOLID原则:单一职责、开闭原则、里式替换、接口隔离原则

SOLID 原则是一组设计原则,旨在创建可维护、可扩展和可重用的软件。这些原则包括单一职责、开闭原则、里式替换、接口隔离和依赖倒置原则,简称为 SOLID。

单一职责原则

单一职责原则指一个类或模块只应该有一个单一的功能,也就是说,一个类或模块只应该有一个单一的责任。如果一个类或模块负责多个不同的任务,那么它将会变得难以维护、扩展和测试。

# 不遵循单一职责原则的代码,既负责保存员工信息到数据库,又负责计算员工税收信息
class Employee:def __init__(self, name, salary):self.name = nameself.salary = salarydef save(self):# 保存员工信息到数据库passdef calculate_tax(self):# 计算员工税收信息pass# 遵循单一职责原则的代码
# 只负责表示员工信息
class Employee:def __init__(self, name, salary):self.name = nameself.salary = salary# 负责保存员工信息到数据库
class EmployeeDB:def save(self, employee):# 保存员工信息到数据库pass# 负责计算员工税收信息
class TaxCalculator:def calculate_tax(self, employee):# 计算员工税收信息pass

开闭原则

开闭原则指一个模块或类应该对扩展开放,对修改关闭。也就是说,应该通过扩展已有的代码来满足新的需求,而不是修改已有的代码。如果一个模块或类需要频繁地修改,那么它将会变得难以维护和测试。

# 不遵循开闭原则的代码
# 矩形
class Rectangle:def __init__(self, width, height):self.width = widthself.height = heightdef set_width(self, width):self.width = widthdef set_height(self, height):self.height = heightdef area(self):return self.width * self.height# 正方形,继承 Rectangle 类
class Square(Rectangle):def __init__(self, side):super().__init__(side, side)# 重写了 set_width() 和 set_height() 方法# 导致在修改正方形的一边时,另一边也会跟着变化,从而破坏了矩形的定义def set_width(self, width):self.width = widthself.height = widthdef set_height(self, height):self.width = heightself.height = height# 遵循开闭原则的代码
# 将 Rectangle 和 Square 类都实现为继承自 Shape 类的子类,再分别实现它们自己的 area() 方法
class Shape:def area(self):passclass Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Square(Shape):def __init__(self, side):self.side = sidedef area(self):return self.side ** 2

里式替换原则

里式替换原则指子类应该能够替换掉它们的父类,并且程序仍然能够正常工作。也就是说,子类应该保持与父类相同的行为,而不是改变其原本的含义。

# 不遵循里式替换原则的代码
class Rectangle:def __init__(self, width, height):self.width = widthself.height = heightdef set_width(self, width):self.width = widthdef set_height(self, height):self.height = heightdef area(self):return self.width * self.heightclass Square(Rectangle):def __init__(self, side):super().__init__(side, side)def set_width(self, width):self.width = widthself.height = widthdef set_height(self, height):self.width = heightself.height = heightdef print_area(rectangle):rectangle.set_width(2)rectangle.set_height(3)print(f"Area: {rectangle.area()}")r = Rectangle(3, 4)
s = Square(3)
print_area(r)  # 输出 "Area: 6"
# 传递一个 Square 对象作为参数时,由于 Square 类重写了 set_width() 和 set_height() 方法,导致输出的面积不是预期的值
print_area(s)  # 输出 "Area: 9"# 遵循里式替换原则
# 可以将 Rectangle 和 Square 类都实现为继承自 Shape 类的子类,并分别实现它们自己的 area() 方法

接口隔离原则

接口隔离原则指一个接口应该只包含被调用者需要的方法,而不应该包含被调用者不需要的方法。也就是说,接口应该尽可能地小,以减少对实现类的依赖和影响。

# 不遵循接口隔离原则的代码
class Document:def __init__(self, content):self.content = contentdef print(self):# 打印文档内容passdef fax(self):# 传真文档内容passdef scan(self):# 扫描文档内容passclass MultiFunctionPrinter:def __init__(self, document):self.document = documentdef print(self):self.document.print()def fax(self):self.document.fax()def scan(self):self.document.scan()class OldFashionedPrinter:def __init__(self, document):self.document = documentdef print(self):self.document.print()# 遵循接口隔离原则的代码
# 将 Printable、Faxable 和 Scanable 接口分别抽象出来,然后让它们各自实现自己需要的方法
class Printable:def print(self):passclass Faxable:def fax(self):passclass Scanable:def scan(self):passclass Document:def __init__(self, content):self.content = contentclass MultiFunctionPrinter(Printable, Faxable, Scanable):def __init__(self, document):self.document = documentdef print(self):self.document.print()def fax(self):self.document.fax()def scan(self):self.document.scan()class OldFashionedPrinter(Printable):def __init__(self, document):self.document = documentdef print(self):self.document.print()

7.2 常见设计模式:工厂模式、单例模式、观察者模式等

工厂模式

工厂模式是一种创建型设计模式,它提供了一种封装对象创建过程的方式。它使用一个共同的接口来创建不同类型的对象,而不必暴露具体的实例化逻辑。

class Shape:def draw(self):passclass Circle(Shape):def draw(self):print("Drawing a circle")class Square(Shape):def draw(self):print("Drawing a square")class ShapeFactory:# 静态方法@staticmethod# 根据传入的形状类型来创建相应的对象def create_shape(shape_type):if shape_type == "circle":return Circle()elif shape_type == "square":return Square()else:raise ValueError("Invalid shape type")# 使用工厂模式创建对象,隐藏了具体的实例化逻辑
shape = ShapeFactory.create_shape("circle")
shape.draw()  # 输出 "Drawing a circle"

单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了全局访问点来获取该实例。

# 单例模式确保在整个应用程序中只有一个实例存在
class Singleton:_instance = None # 静态变量 # 静态方法@staticmethoddef get_instance():if Singleton._instance is None:Singleton._instance = Singleton()return Singleton._instance# 使用单例模式获取对象实例
singleton1 = Singleton.get_instance()
singleton2 = Singleton.get_instance()print(singleton1 is singleton2)  # 输出 True

观察者模式

观察者模式是一种行为型设计模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。

# 主题类
class Subject:def __init__(self):# _observers 列表来保存所有注册的观察者对象self._observers = []# 添加def attach(self, observer):self._observers.append(observer)# 移除def detach(self, observer):self._observers.remove(observer)# 通知def notify(self):for observer in self._observers:observer.update()# 观察者类
class Observer:# 用于接收并处理通知def update(self):pass# 具体的观察者类
class ConcreteObserver(Observer):def update(self):print("Received notification")# 使用观察者模式
subject = Subject()
observer = ConcreteObserver()
subject.attach(observer)
# 所有已注册的观察者都会接收到通知并执行相应的操作
subject.notify()  # 输出 "Received notification"

八、实践项目:创建一个简单的图书管理系统

class Book:def __init__(self, title, author):self.title = titleself.author = authorself.is_borrowed = False# 借阅图书def borrow(self):if self.is_borrowed:print("This book is already borrowed.")else:self.is_borrowed = Trueprint("Successfully borrowed the book:", self.title)# 归还图书def return_book(self):if self.is_borrowed:self.is_borrowed = Falseprint("Successfully returned the book:", self.title)else:print("This book is not currently borrowed.")class Library:def __init__(self):self.books = []# 添加图书def add_book(self, book):self.books.append(book)print("Added book:", book.title)# 删除图书def remove_book(self, book):if book in self.books:self.books.remove(book)print("Removed book:", book.title)else:print("Book not found in the library.")# 显示图书def display_books(self):if len(self.books) == 0:print("No books in the library.")else:print("Books in the library:")for book in self.books:print(book.title, "by", book.author)# 创建图书和图书馆对象
book1 = Book("Python Crash Course", "Eric Matthes")
book2 = Book("Clean Code", "Robert C. Martin")
book3 = Book("The Pragmatic Programmer", "Andrew Hunt and David Thomas")library = Library()# 添加图书到图书馆
library.add_book(book1)
library.add_book(book2)
library.add_book(book3)# 显示图书馆的所有图书
library.display_books()# 借阅一本图书
book2.borrow()# 再次显示图书馆的所有图书
library.display_books()# 归还图书
book2.return_book()# 再次显示图书馆的所有图书
library.display_books()

你要成长,绝处也能逢生

相关文章:

Python面向对象编程:探索代码的结构之美

文章目录 一、引言二、为什么学习面向对象编程2.1 提高代码的可维护性&#xff1a;通过封装、继承和多态实现模块化设计2.2 提升代码的复用性&#xff1a;通过类和对象的创建实现代码的重用 三、类和对象的基本概念3.1 类和对象的定义和关系&#xff1a;类是对象的模板&#xf…...

Java基于SpringBoot+Vue的电影影城管理系统,附源码,文档

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

【学网攻】 第(14)节 -- 动态路由(EIGRP)

系列文章目录 目录 系列文章目录 文章目录 前言 一、动态路由EIGRP是什么&#xff1f; 二、实验 1.引入 实验步骤 实验拓扑图 实验配置 看到D开头是便是我们的EIGRP动态路由 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学…...

【Linux】多线程(线程概念+线程控制)

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…...

【昕宝爸爸小模块】深入浅出详解之常见的语法糖

深入浅出详解之常见的语法糖 一、&#x1f7e2;关于语法糖的典型解析二、&#x1f7e2;如何解语法糖&#xff1f;2.1&#x1f7e2;糖块一、switch 支持 String 与枚举2.2&#x1f4d9;糖块二、泛型2.3&#x1f4dd;糖块三、自动装箱与拆箱2.4&#x1f341;糖块四、方法变长参数…...

低代码

腾讯云微搭低代码 WeDa _低代码开发平台_可视化开发平台-腾讯云 首页 - 钉钉宜搭 快速上手多维表格 爱速搭 - 企业应用智能设计平台 | 低代码平台 - 百度智能云 Astro轻应用 Astro Zero_低代码开发平台_软件开发工具_应用开发工具_华为云 低代码是一种软件开发方法&#x…...

2024/1/30 备战蓝桥杯 3-1 栈

目录 小鱼的数字游戏 P1427 小鱼的数字游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 表达式括号匹配 P1739 表达式括号匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 【模板】栈 B3614 【模板】栈 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 小鱼的数字…...

qt setStyleSheet 设置多个属性{}之间用空格间隔

setStyleSheet 设置多个属性时&#xff0c;大属性之间不能用分号&#xff0c;用 空格进行间隔 pbtn1->setStyleSheet("QPushButton {background-color: rgb(4,138,224);font: bold 12pt;color: rgb(255,255,255);} QPushButton:hover,QPushButton:pushed {background-…...

【Node.js基础】Node.js的介绍与安装

文章目录 前言一、什么是Node.js&#xff1f;二、安装Node.js2.1 Windows系统2.2 macOS系统2.3 Linux系统 三、运行js代码总结 前言 随着互联网技术的不断发展&#xff0c;构建高性能、实时应用的需求日益增长。Node.js作为一种服务器端运行时环境&#xff0c;以其事件驱动、非…...

树和二叉树基础

树和二叉树基础 1.1树的概念 树是在数据结构中第一次接触到的非线性结构。 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它 叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&am…...

第十四届蓝桥杯大赛软件赛省赛(C/C++ 大学B组)题解

尝试再做一次&#xff0c;我记得还是有点难&#xff0c;我会尽量多写一点解析&#xff0c;尽量让基础比较弱的友友也能看懂&#xff0c;希望能给你带来帮助 目录 1. 日期统计 题目描述 解题思路 具体代码 2. 01 串的熵 题目描述 解题思路 具体代码 3. 冶炼金属 题目…...

【计算机网络】网络的网络

网络的网络 客户 customer 接入ISP提供商 provider 全球承载ISP多个ISP的层级结构 第一层ISP &#xff08;tier-1 ISP &#xff09; 位于顶部 区域ISP &#xff08;reginal ISP&#xff09;Level 3通信 &#xff0c;AT&T&#xff0c;Sprint &#xff0c;NTT存在点&#x…...

SQL Server 函数参考手册

目录 SQL Server 字符串函数 SQL Server 数值函数 SQL Server 日期函数 SQL Server 高级函数 SQL Server 字符串函数 函数描述ASCII返回特定字符的 ASCII 值CHAR根据ASCII码返回字符CHARINDEX返回子字符串在字符串中的位置CONCAT将两个或多个字符串加在一起Concat with 将…...

NTP时间同步服务器@客户端时钟同步设置

NTP时间同步服务器客户端时钟同步设置 时间同步服务器支持NTP和SNTP网络同步协议&#xff0c;是一款高精度、大容量、高品质的时钟产品。设备采用冗余架构设计&#xff0c;高精度时钟直接来源于北斗、GPS系统中各个卫星的原子钟&#xff0c;通过信号解析驯服本地时钟源&#x…...

flask_django基于python的城市轨道交通公交线路查询系统vue

同时&#xff0c;随着信息社会的快速发展&#xff0c;城市轨道交通线路查询系统面临着越来越多的信息&#xff0c;因此很难获得他们对高效信息的需求&#xff0c;如何使用方便快捷的方式使查询者在广阔的海洋信息中查询&#xff0c;存储&#xff0c;管理和共享信息方面有效&…...

【Spring连载】使用Spring Data访问Redis(四)----RedisTemplate

【Spring连载】使用Spring Data访问Redis&#xff08;四&#xff09;----RedisTemplate通过RedisTemplate处理对象Working with Objects through RedisTemplate 一、专注String的便利类二、Serializers 大多数用户可能使用RedisTemplate及其相应的包org.springframework.data.r…...

WriteFlow写作流GPT应用,激发创意的写作助手

写作是一项充满挑战的任务&#xff0c;有时我们会遇到写作灵感枯竭、思路混乱、语言表达困难等问题。为了帮助人们克服这些困难&#xff0c;我创建了一个名为WriteFlow的写作工具&#xff0c;它是一个基于GPT技术的智能助手&#xff0c;旨在激发创意&#xff0c;提供Prompt提示…...

matlab对负数开立方根得到虚数的解决方案

问题描述&#xff1a;在matlab中&#xff0c;对负数开立方根&#xff0c;不出意外你将得到虚数。 例如 − 27 3 \sqrt[3]{-27} 3−27 ​&#xff0c;我们知道其实数解是-3&#xff0c;但在matlab中的计算结果如下&#xff1a; 问题原因&#xff1a;matlab中的立方根运算是在…...

NFTScan 与 OneID 达成合作伙伴,支持多类型 DID 搜索!

近日&#xff0c;NFT 数据基础设施 NFTScan 与一体化数字身份解决方案 OneID 达成合作伙伴关系&#xff0c;双方将在 NFT 数据层面展开合作。为 Web3 用户带来优质的 NFT 搜索查询交互体验&#xff0c;向更安全和更有效的去中心化生态系统迈出的重要一步。 NFTScan 浏览器现已支…...

c# textbox 提示文字

1. 定义提示文字内容 private readonly string RemarkText "最多输入100字"; // 提示文字 2. 添加textbox 焦点事件&#xff0c; 初始化textbox提示文字和字体颜色 public UserControl(){InitializeComponent();tb_Remark.Text RemarkText;tb_Remark.ForeColor…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用

中达瑞和自2005年成立以来&#xff0c;一直在光谱成像领域深度钻研和发展&#xff0c;始终致力于研发高性能、高可靠性的光谱成像相机&#xff0c;为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

二叉树-144.二叉树的前序遍历-力扣(LeetCode)

一、题目解析 对于递归方法的前序遍历十分简单&#xff0c;但对于一位合格的程序猿而言&#xff0c;需要掌握将递归转化为非递归的能力&#xff0c;毕竟递归调用的时候会调用大量的栈帧&#xff0c;存在栈溢出风险。 二、算法原理 递归调用本质是系统建立栈帧&#xff0c;而非…...

【Vue】scoped+组件通信+props校验

【scoped作用及原理】 【作用】 默认写在组件中style的样式会全局生效, 因此很容易造成多个组件之间的样式冲突问题 故而可以给组件加上scoped 属性&#xff0c; 令样式只作用于当前组件的标签 作用&#xff1a;防止不同vue组件样式污染 【原理】 给组件加上scoped 属性后…...