面向对象的三大特征:封装、继承、多态

面向对象编程基础

一、封装(Encapsulation)

1. 封装的定义

封装是面向对象编程的核心概念之一,其主要目的是隐藏对象的内部细节,仅对外提供必要的操作接口。通过封装,可以保护对象的内部状态,防止外部直接访问和修改,从而提高代码的安全性和可维护性。

封装的核心在于:

  • 隐藏内部实现:将对象的内部数据和实现细节隐藏起来,外部代码无法直接访问。
  • 提供操作接口:通过公有方法(Public Methods)对外提供操作接口,允许外部代码通过这些接口与对象交互。

2. 权限控制

在 Python 中,可以通过对属性或方法添加单下划线、双下划线以及首尾双下划线来实现权限控制。

2.1 单下划线开头(Protected)

以单下划线开头的属性或方法表示受保护的成员(Protected)。这类成员被视为仅供内部使用,允许类本身和子类进行访问,但不建议外部代码直接访问。

示例:

class Student:
    def __init__(self, name, age):
        self._name = name  # 受保护的属性
        self._age = age    # 受保护的属性

    def _display_info(self):  # 受保护的方法
        print(f"Name: {self._name}, Age: {self._age}")

2.2 双下划线开头(Private)

以双下划线开头的属性或方法表示私有的成员(Private)。这类成员只允许定义该属性或方法的类本身进行访问。Python 通过名称重整(Name Mangling)来实现私有化。

示例:

class Student:
    def __init__(self, name, age):
        self.__name = name  # 私有属性
        self.__age = age    # 私有属性

    def __display_info(self):  # 私有方法
        print(f"Name: {self.__name}, Age: {self.__age}")

2.3 首尾双下划线(Special Methods)

以首尾双下划线开头和结尾的属性或方法通常表示特殊的方法(如 __init____str__ 等),这些方法有特殊的语义和用途。

示例:

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

    def __str__(self):
        return f"Student(name={self.name}, age={self.age})"

3. 示例:封装的实现

3.1 权限控制

class Student:
    def __init__(self, name, age, gender):
        self._name = name  # 受保护的属性
        self.__age = age   # 私有属性
        self.gender = gender  # 公有属性

    def _display_info(self):  # 受保护的方法
        print(f"Name: {self._name}, Age: {self.__age}")

    def __display_private_info(self):  # 私有方法
        print(f"Private Info: {self.__age}")

    def show(self):  # 公有方法
        self._display_info()
        self.__display_private_info()
        print(f"Gender: {self.gender}")

# 创建对象
stu = Student("Kyle", 20, "man")

# 访问受保护的属性和方法
print(stu._name)  # 输出:Kyle
stu._display_info()  # 输出:Name: Kyle, Age: 20

# 尝试访问私有属性和方法(不推荐)
print(stu._Student__age)  # 输出:20
stu._Student__display_private_info()  # 输出:Private Info: 20

# 调用公有方法
stu.show()

输出结果:

Kyle
Name: Kyle, Age: 20
Name: Kyle, Age: 20
Private Info: 20
Gender: man

3.2 属性的设置与访问控制

class Student:
    def __init__(self, name, gender):
        self.name = name
        self.__gender = gender  # 私有属性

    @property
    def gender(self):
        """获取性别属性"""
        return self.__gender

    @gender.setter
    def gender(self, value):
        """设置性别属性,添加校验逻辑"""
        if value not in ["man", "woman"]:
            print("性别有误,已将性别默认设置为 woman")
            self.__gender = "woman"
        else:
            self.__gender = value

# 创建对象
stu = Student("Kyle", "man")
print(f"{stu.name} 的性别是 {stu.gender}")  # 输出:Kyle 的性别是 man

# 尝试修改性别属性
stu.gender = "other"  # 性别有误,已将性别默认设置为 woman
print(f"{stu.name} 的性别是 {stu.gender}")  # 输出:Kyle 的性别是 woman

输出结果:

Kyle 的性别是 man
性别有误,已将性别默认设置为 woman
Kyle 的性别是 woman

3.3 定义一个 BankAccount

class BankAccount:
    def __init__(self, owner, balance=0):
        self.__owner = owner  # 私有属性:账户所有者
        self.__balance = balance  # 私有属性:账户余额

    def deposit(self, amount):  # 公有方法:存款
        if amount > 0:
            self.__balance += amount
            print(f"Deposited {amount}. New balance: {self.__balance}")
        else:
            print("Invalid deposit amount.")

    def withdraw(self, amount):  # 公有方法:取款
        if amount > 0 and amount <= self.__balance:
            self.__balance -= amount
            print(f"Withdrew {amount}. New balance: {self.__balance}")
        else:
            print("Invalid withdrawal amount or insufficient funds.")

    def get_balance(self):  # 公有方法:获取余额
        return self.__balance

    def __str__(self):  # 特殊方法:打印对象信息
        return f"BankAccount(owner={self.__owner}, balance={self.__balance})"

# 创建 BankAccount 对象并操作
account = BankAccount("Alice", 100)
account.deposit(50)  # 输出:Deposited 50. New balance: 150
account.withdraw(30)  # 输出:Withdrew 30. New balance: 120
print(account.get_balance())  # 输出:120
print(account)  # 输出:BankAccount(owner=Alice, balance=120)

4. 封装的优势

  1. 隐藏内部实现:封装使得对象的内部实现细节对外部不可见,仅通过公开的接口进行交互,降低了代码的耦合度。
  2. 保护数据安全:通过私有化属性和方法,防止外部代码直接访问和修改对象的内部状态,避免数据被错误操作。
  3. 提供统一的接口:封装允许开发者通过统一的接口操作对象,而不必关心具体的实现细节,提高了代码的可读性和可维护性。

5. 总结

封装是面向对象编程的核心概念之一,通过权限控制(如单下划线、双下划线等)和属性访问控制(如 @property@<属性名>.setter),可以有效地隐藏对象的内部细节,保护数据安全,同时提供统一的接口供外部使用。掌握封装的使用方法,可以帮助你更好地设计和实现面向对象的程序。


二、继承(Inheritance)

1. 定义

  • 继承:允许一个类(子类)继承另一个类(父类)的属性和方法。
  • 子类:继承了父类的属性和方法,并可以添加新的属性和方法或修改父类的方法。
  • 父类:被继承的类。
  • 一个子类可以继承多个父类;一个父类也可以拥有多个子类;如果一个类没有继承任何类,那么这个类默认继承 object 类。

2. 语法结构

class 类名(父类1, 父类2, ..., 父类N):
    pass

3. 示例:定义一个 Student 类,继承自 Person

class Person:   # 默认继承 object
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'Hello everyone, my name is {self.name} and I am {self.age} years old.')

class Student(Person):  # Student 类继承 Person 类
    def __init__(self, name, age, stuno):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.stuno = stuno  # 新增属性:学生ID

    def study(self):  # 新增方法:学习
        print(f"{self.name} is studying with student ID {self.stuno}.")

创建 Student 对象

# 创建一个 Student 对象
student1 = Student("Bob", 20, "S12345")

# 调用继承自父类的方法
student1.show()  # 输出:Hello everyone, my name is Bob and I am 20 years old.

# 调用子类新增的方法
student1.study()  # 输出:Bob is studying with student ID S12345.

4. 多级继承示例:一个父类多个子类

class Teacher(Person):  # 另一个子类,继承自 Person
    def __init__(self, name, age, subject):
        super().__init__(name, age)  # 调用父类的构造方法
        self.subject = subject  # 新增属性:教授的科目

    def teach(self):  # 新增方法:教学
        print(f"{self.name} is teaching {self.subject}.")

创建 Teacher 对象

teacher1 = Teacher("Alice", 35, "Mathematics")

# 调用继承自父类的方法
teacher1.show()  # 输出:Hello everyone, my name is Alice and I am 35 years old.

# 调用子类新增的方法
teacher1.teach()  # 输出:Alice is teaching Mathematics.

5. 多继承示例:一个子类多个父类

class FatherA:
    def __init__(self, name):
        self.name = name

    def showA(self):
        print('父类 A 中的方法')

class FatherB:
    def __init__(self, age):
        self.age = age

    def showB(self):
        print('父类 B 中的方法')

# 多继承
class Son(FatherA, FatherB):
    def __init__(self, name, age, gender):
        # 需要调用两个父类的初始化方法,使用父类的名称来区别
        FatherA.__init__(self, name)
        FatherB.__init__(self, age)
        self.gender = gender

    def show(self):
        print(f'My name is {self.name}, age {self.age}, and I am a {self.gender}.')

测试多继承

son = Son('Kyle', 20, 'man')
son.showA()  # 输出:父类 A 中的方法
son.showB()  # 输出:父类 B 中的方法
son.show()   # 输出:My name is Kyle, age 20, and I am a man.

6. 方法重写

  • 子类继承了父类就拥有了父类中公有成员和受保护的成员。
  • 父类的方法可能并不能完全适合子类的需求,这时子类可以重写父类的方法。
  • 子类在重写父类的方法时,要求方法的名称必须与父类方法的名称相同。在子类重写后的方法中,可以通过 super().xxx() 调用父类中的方法。
class Person:   # 默认继承 object
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'Hello everyone, my name is {self.name} and I am {self.age} years old.')

# Student 类继承 Person 类
class Student(Person):
    def __init__(self, name, age, stuno):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.stuno = stuno

    def show(self):
        # 调用父类中的方法
        super().show()
        print(f'My student ID is {self.stuno}.')

# Doctor 类继承 Person 类
class Doctor(Person):
    def __init__(self, name, age, department):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.department = department

    def show(self):
        # 调用父类中的方法
        # super().show()
        print(f'Hello everyone, my name is {self.name}, I am {self.age} years old, and I work in the {self.department} department.')

测试方法重写

stu = Student('Kyle', 20, '1001')
stu.show()
# 输出:
# Hello everyone, my name is Kyle and I am 20 years old.
# My student ID is 1001.

doctor = Doctor('Ming', 25, 'WK')
doctor.show()
# 输出:
# Hello everyone, my name is Ming, I am 25 years old, and I work in the WK department.

7. 总结

继承是面向对象编程的重要特性之一,它允许子类继承父类的属性和方法,从而实现代码复用。通过继承,可以构建层次化的类结构,提高代码的可维护性和可扩展性。然而,过度使用继承可能导致代码复杂化,因此在设计类时应合理使用继承,避免多级继承过深。


三、多态(Polymorphism)

1. 定义

多态是面向对象编程中的一个重要概念,它允许不同类的对象对同一消息做出响应,即同一个接口可以被不同的底层实现替换。通俗理解就是:同一个方法名,不同的行为。多态的核心在于动态决定使用哪个对象的方法,从而实现程序的可扩展性。

  • 多态的特性
    • 接口一致性:不同类的对象可以共享同一个接口(方法名)。
    • 行为多样性:同一个接口在不同类中可以有不同的实现。
    • 动态绑定:在运行时根据对象的实际类型调用相应的方法。

2. 示例:定义 Animal 类及其子类

class Animal:
    def __init__(self, name):
        self.name = name   # 初始化 name 属性

    def make_sound(self):  # 父类中的方法
        print("Some generic animal sound.")

class Dog(Animal):
    def make_sound(self):  # 重写父类方法
        print("Woof!")

class Cat(Animal):
    def make_sound(self):  # 重写父类方法
        print("Meow!")

3. 多态的体现

3.1 不同对象调用相同方法

# 创建 Animal 对象
animal1 = Animal("Generic Animal")  # 传入 "Generic Animal" 作为 name
animal1.make_sound()  # 输出:Some generic animal sound.

# 创建 Dog 对象
dog1 = Dog("Buddy")  # 传入 "Buddy" 作为 name
dog1.make_sound()  # 输出:Woof!

# 创建 Cat 对象
cat1 = Cat("Whiskers")  # 传入 "Whiskers" 作为 name
cat1.make_sound()  # 输出:Meow!

3.2 通过函数体现多态

# 定义一个函数,接受一个 Animal 类型的对象
def animal_sound(animal: Animal):
    animal.make_sound()

# 传入不同类型的对象
animal_sound(animal1)  # 输出:Some generic animal sound.
animal_sound(dog1)     # 输出:Woof!
animal_sound(cat1)     # 输出:Meow!

示例:使用 name 属性

class Animal:
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print(f"{self.name} makes a generic animal sound.")

class Dog(Animal):
    def make_sound(self):
        print(f"{self.name} says Woof!")

class Cat(Animal):
    def make_sound(self):
        print(f"{self.name} says Meow!")

创建对象并调用方法

animal1 = Animal("Generic Animal")
animal1.make_sound()  # 输出:Generic Animal makes a generic animal sound.

dog1 = Dog("Buddy")
dog1.make_sound()  # 输出:Buddy says Woof!

cat1 = Cat("Whiskers")
cat1.make_sound()  # 输出:Whiskers says Meow!

3.3 多态的优势

  • 代码复用:通过多态,可以编写通用的代码,而无需关心具体的对象类型。
  • 扩展性:新增类时,只要遵循相同的接口,无需修改现有代码。
  • 灵活性:可以在运行时动态决定调用哪个类的方法。

4. 更多示例:多态的应用

4.1 不同类的同名方法

class Person:
    def eat(self):
        print('五谷杂粮')

class Cat:
    def eat(self):
        print('鱼')

class Dog:
    def eat(self):
        print('骨头')

4.2 通过函数调用体现多态

# 定义一个函数,接受一个对象并调用其 eat 方法
def fun(obj):
    obj.eat()

# 创建三个类的对象
per = Person()
cat = Cat()
dog = Dog()

# 调用 fun 函数
fun(per)  # 输出:五谷杂粮
fun(cat)  # 输出:鱼
fun(dog)  # 输出:骨头

4.3 使用循环体现多态

# 将不同类的对象放入列表
animals = [per, cat, dog]

# 遍历列表,调用每个对象的 eat 方法
for item in animals:
    item.eat()

输出结果:

五谷杂粮
鱼
骨头

5. 多态的实现机制

5.1 方法重写(Override)

子类重写父类的方法,使得同一个方法名在不同类中可以有不同的实现。这是多态的基础。

class Animal:
    def make_sound(self):
        print("Some generic animal sound.")

class Dog(Animal):
    def make_sound(self):
        print("Woof!")

class Cat(Animal):
    def make_sound(self):
        print("Meow!")

5.2 动态绑定

Python 在运行时根据对象的实际类型调用相应的方法,而不是根据变量的声明类型。这使得多态成为可能。

def animal_sound(animal: Animal):
    animal.make_sound()

animal_sound(Dog())  # 输出:Woof!
animal_sound(Cat())  # 输出:Meow!
# 等效于
dog = Dog()
cat = Cat()

animal_sound(dog)  # 输出:Woof!
animal_sound(cat)  # 输出:Meow!

---------

# 函数调用过程
    创建对象:
      Dog() 创建了一个 Dog 类的对象。
      Cat() 创建了一个 Cat 类的对象。
      传递对象:
      animal_sound(Dog()) 将 Dog 类的对象传递给 animal_sound 函数。
      animal_sound(Cat()) 将 Cat 类的对象传递给 animal_sound 函数。
      函数内部:
      在 animal_sound 函数中,animal 是一个 Animal 类型的对象(或者其子类的对象)。
      调用 animal.make_sound() 时,Python 会根据 animal 的实际类型(Dog 或 Cat)调用相应的方法。
  • animal: Animal 是一种类型注解

  • animal:这是函数的参数名,表示函数接收一个参数。

  • Animal:这是参数的类型注解,表示传入的参数应该是一个 Animal 类型的对象(或者其子类的对象)。

通过这种方式,animal_sound 函数明确地表示它接收一个 Animal 类型的对象,并调用该对象的 make_sound 方法。

6. 多态的应用场景

6.1 图形绘制

假设有一个图形类的层次结构,每个图形都有一个 draw 方法。通过多态,可以编写通用的代码来绘制不同类型的图形。

class Shape:
    def draw(self):
        print("Drawing a generic shape.")

class Circle(Shape):
    def draw(self):
        print("Drawing a circle.")

class Square(Shape):
    def draw(self):
        print("Drawing a square.")

def draw_shape(shape: Shape):
    shape.draw()

draw_shape(Circle())  # 输出:Drawing a circle.
draw_shape(Square())  # 输出:Drawing a square.

6.2 用户界面组件

假设有一个组件类的层次结构,每个组件都有一个 render 方法。通过多态,可以编写通用的代码来渲染不同类型的组件。

class Component:
    def render(self):
        print("Rendering a generic component.")

class Button(Component):
    def render(self):
        print("Rendering a button.")

class Label(Component):
    def render(self):
        print("Rendering a label.")

def render_component(component: Component):
    component.render()

render_component(Button())  # 输出:Rendering a button.
render_component(Label())   # 输出:Rendering a label.

7. 总结

多态是面向对象编程的核心特性之一,它允许不同类的对象对同一消息做出响应,即同一个接口可以被不同的底层实现替换。通过多态,可以编写通用的代码,提高程序的可扩展性和灵活性。多态的实现基于方法重写和动态绑定,使得同一个方法名在不同类中可以有不同的行为。掌握多态的使用方法,可以帮助你更好地设计和实现面向对象的程序。


posted @ 2025-04-04 23:53  kyle_7Qc  阅读(1164)  评论(0)    收藏  举报