100天带你精通Python——第17天 面向对象编程(二):封装、继承与多态

文章目录


前言

昨天我们揭开了面向对象编程的"第一层面纱"------理解了类与对象的关系。但真正的OOP威力,藏在它的三大核心特性 中:封装、继承与多态。这三者如同武侠小说中的"内功心法":

  • 🔒 封装 = 守住核心机密(隐藏内部实现)
  • 🧬 继承 = 传承家族绝学(代码复用)
  • 🦆 多态 = 一招多用(灵活扩展)

掌握它们,你才能写出像Django、Flask这样的高质量框架代码。今天我们将用生活化比喻+电商实战案例,彻底搞懂这三大特性。准备好了吗?我们出发!


一、封装:给数据穿上"防护甲"

1. 为什么需要封装?

想象你买了一台智能音箱:

  • ✅ 你会用语音指令控制它(公开接口
  • ❌ 但你不会直接拆开主板修改电路(内部实现

封装的核心思想:隐藏内部细节,只暴露必要接口。这样做的好处:

  • 安全性:防止外部随意修改关键数据
  • 简单性:用户只需知道"怎么用",不用关心"怎么实现"
  • 可维护性:内部实现改动不影响外部调用

2. Python中的封装实现

(1)私有属性:用双下划线"上锁"

python 复制代码
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner          # 公有属性:谁都能访问
        self.__balance = balance    # 私有属性:外部无法直接访问
    
    def deposit(self, amount):
        """存款"""
        if amount > 0:
            self.__balance += amount
            print(f"✓ 存入{amount}元,当前余额:{self.__balance}元")
        else:
            print("✗ 存款金额必须大于0")
    
    def withdraw(self, amount):
        """取款"""
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"✓ 取出{amount}元,当前余额:{self.__balance}元")
        else:
            print(f"✗ 余额不足!当前余额:{self.__balance}元")
    
    def get_balance(self):
        """安全查询余额"""
        return self.__balance

# 使用示例
account = BankAccount("张三", 1000)
account.deposit(500)      # ✓ 存入500元
account.withdraw(2000)    # ✗ 余额不足!
print(account.owner)      # 张三(公有属性可直接访问)
# print(account.__balance)  # 报错!私有属性无法直接访问
print(account.get_balance())  # 1500(通过方法安全访问)

🔍 技术细节:Python的"私有"是伪私有__balance实际被改名为_BankAccount__balance,可通过account._BankAccount__balance访问。但这属于"破坏封装",强烈不建议在正常代码中使用。

(2)property:优雅的属性访问控制

私有属性+getter/setter方法略显繁琐,Python提供@property装饰器实现属性式访问

python 复制代码
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius  # 单下划线:约定为"内部使用"(非强制私有)
    
    @property
    def celsius(self):
        """getter:像访问属性一样获取值"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """setter:像赋值属性一样设置值,并做校验"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """自动计算华氏度(只读属性)"""
        return self._celsius * 9/5 + 32

# 使用示例
temp = Temperature(25)
print(temp.celsius)      # 25(像访问属性,实际调用getter)
print(temp.fahrenheit)   # 77.0(自动计算)
temp.celsius = 30        # 像赋值属性,实际调用setter
# temp.celsius = -300    # 抛出ValueError!

💡 最佳实践:

  • _xxx表示"内部使用"(约定俗成,不强制)
  • __xxx实现强封装(防意外覆盖)
  • @property提供优雅的访问控制接口

二、继承:代码的"基因传承"

1. 单继承:子承父业

继承让子类自动获得父类的属性和方法,避免重复造轮子。

python 复制代码
# 父类:通用商品
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def show_info(self):
        return f"商品:{self.name} | 价格:¥{self.price}"
    
    def apply_discount(self, rate):
        """通用折扣逻辑"""
        self.price *= (1 - rate)
        print(f"✓ {self.name}已打折,现价:¥{self.price:.2f}")

# 子类:图书(继承Product)
class Book(Product):
    def __init__(self, name, price, author, isbn):
        super().__init__(name, price)  # 调用父类构造方法
        self.author = author
        self.isbn = isbn
    
    # 重写父类方法:图书专属信息展示
    def show_info(self):
        base_info = super().show_info()  # 复用父类逻辑
        return f"{base_info} | 作者:{self.author} | ISBN:{self.isbn}"
    
    # 子类特有方法
    def lend(self, days):
        print(f"✓ 《{self.name}》已借出{days}天")

# 子类:电子产品
class Electronic(Product):
    def __init__(self, name, price, warranty_years):
        super().__init__(name, price)
        self.warranty_years = warranty_years
    
    # 重写折扣逻辑:电子产品最多打8折
    def apply_discount(self, rate):
        if rate > 0.2:
            print(f"⚠ 电子产品折扣上限20%,自动调整为20%")
            rate = 0.2
        super().apply_discount(rate)

# 使用示例
book = Book("Python编程", 89, "张三", "978-7-111-12345-6")
phone = Electronic("智能手机", 2999, 2)

print(book.show_info())
# 商品:Python编程 | 价格:¥89 | 作者:张三 | ISBN:978-7-111-12345-6

book.apply_discount(0.3)  # ✓ Python编程已打折,现价:¥62.30
phone.apply_discount(0.5) # ⚠ 电子产品折扣上限20%... ✓ 智能手机已打折,现价:¥2399.20

🔑 关键点:

  • super():安全调用父类方法,避免硬编码父类名
  • 方法重写(Override):子类提供父类方法的新实现
  • is-a关系:Book is a Product(继承的语义基础)

2. 多继承:融合多方"血脉"

Python支持一个子类继承多个父类(Java/C#不支持),但需谨慎使用。

python 复制代码
class Flyable:
    def fly(self):
        print(f"{self.name}正在飞行 ✈️")

class Swimmable:
    def swim(self):
        print(f"{self.name}正在游泳 🏊")

# 多继承:水陆空三栖生物
class AmphibiousVehicle(Flyable, Swimmable):
    def __init__(self, name):
        self.name = name

vehicle = AmphibiousVehicle("变形金刚")
vehicle.fly()   # 变形金刚正在飞行 ✈️
vehicle.swim()  # 变形金刚正在游泳 🏊

⚠️ 警告:多继承易引发钻石继承问题(多个父类有同名方法)。解决方案:

  1. 尽量用组合替代继承(见下文最佳实践)
  2. ClassName.__mro__查看方法解析顺序(MRO)
  3. 优先使用单继承+接口设计

三、多态:同一接口,多种实现

1. 什么是多态?

"多态" = 同一个操作作用于不同对象,产生不同行为。

核心价值:编写通用代码,无需关心具体类型

python 复制代码
# 定义统一接口
class Payment:
    def pay(self, amount):
        raise NotImplementedError("子类必须实现pay方法")

class Alipay(Payment):
    def pay(self, amount):
        print(f"✓ 支付宝支付成功:¥{amount}")

class WechatPay(Payment):
    def pay(self, amount):
        print(f"✓ 微信支付成功:¥{amount}")

class BankCard(Payment):
    def pay(self, amount):
        print(f"✓ 银行卡支付成功:¥{amount}")

# 多态应用:支付中心不关心具体支付方式
def checkout(payment_method: Payment, total):
    print(f"开始支付 ¥{total}...")
    payment_method.pay(total)  # 同一接口,不同实现
    print("支付完成!")

# 三种支付方式,同一套流程
checkout(Alipay(), 199)      # ✓ 支付宝支付成功:¥199
checkout(WechatPay(), 89)    # ✓ 微信支付成功:¥89
checkout(BankCard(), 500)    # ✓ 银行卡支付成功:¥500

2. 鸭子类型:Python的"隐式多态"

Python不强制要求继承同一父类,只要"像鸭子一样走路、叫",就是鸭子:

python 复制代码
class Dog:
    def speak(self):
        return "汪汪!"

class Cat:
    def speak(self):
        return "喵喵!"

class Robot:
    def speak(self):
        return "哔哔!"

# 不检查类型,只看是否有speak方法
def make_sound(animal):
    print(animal.speak())

make_sound(Dog())   # 汪汪!
make_sound(Cat())   # 喵喵!
make_sound(Robot()) # 哔哔!

💡 鸭子类型优势:

  • 更灵活:无需设计复杂的继承体系
  • 更Pythonic:符合"EAFP"原则(Easier to Ask for Forgiveness than Permission)
  • 代价:需靠文档/类型注解保证接口一致性(Python 3.5+推荐用typing模块)

四、综合实战:电商商品管理系统

融合三大特性,构建可扩展的商品管理模块:

python 复制代码
from abc import ABC, abstractmethod
from typing import List

# 抽象基类:强制子类实现核心接口(封装+多态)
class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, price: float) -> float:
        pass

# 具体策略:不同折扣算法(多态)
class NoDiscount(DiscountStrategy):
    def calculate(self, price): return price

class SeasonDiscount(DiscountStrategy):
    def __init__(self, rate=0.2): self.rate = rate
    def calculate(self, price): return price * (1 - self.rate)

class VipDiscount(DiscountStrategy):
    def calculate(self, price): return price * 0.85 if price > 100 else price

# 商品基类(封装)
class Product:
    def __init__(self, sku: str, name: str, price: float):
        self._sku = sku
        self._name = name
        self._original_price = price
        self._discount_strategy = NoDiscount()  # 默认无折扣
    
    @property
    def final_price(self):
        """最终价格(封装计算逻辑)"""
        return self._discount_strategy.calculate(self._original_price)
    
    def set_discount(self, strategy: DiscountStrategy):
        """动态切换折扣策略(策略模式)"""
        self._discount_strategy = strategy
    
    def show(self):
        disc = f" (¥{self._original_price}→¥{self.final_price:.2f})" if self.final_price < self._original_price else ""
        return f"[{self._sku}] {self._name} ¥{self.final_price:.2f}{disc}"

# 具体商品(继承)
class Book(Product):
    def __init__(self, sku, name, price, author):
        super().__init__(sku, name, price)
        self.author = author
    
    def show(self):
        return super().show() + f" | 作者:{self.author}"

class Electronic(Product):
    def __init__(self, sku, name, price, warranty):
        super().__init__(sku, name, price)
        self.warranty = warranty
    
    # 重写折扣逻辑:电子产品自动应用季节折扣
    def set_discount(self, strategy):
        if isinstance(strategy, SeasonDiscount):
            super().set_discount(strategy)
        else:
            print(f"⚠ {self._name}仅支持季节折扣")
    
    def show(self):
        return super().show() + f" | 保修:{self.warranty}年"

# 购物车(多态应用)
class ShoppingCart:
    def __init__(self):
        self.items: List[Product] = []
    
    def add(self, item: Product):
        self.items.append(item)
    
    def checkout(self):
        print("\n===== 购物车结算 =====")
        total = 0
        for item in self.items:
            print(item.show())
            total += item.final_price
        print(f"----------------------")
        print(f"总计:¥{total:.2f}\n")

# 使用示例
cart = ShoppingCart()
cart.add(Book("B001", "Python入门", 69, "李四"))
cart.add(Electronic("E001", "无线耳机", 299, 1))

# 动态应用折扣
cart.items[0].set_discount(SeasonDiscount(0.3))  # 图书打7折
cart.items[1].set_discount(SeasonDiscount(0.2))  # 电子产品打8折

cart.checkout()
"""
===== 购物车结算 =====
[B001] Python入门 ¥48.30 (¥69→¥48.30) | 作者:李四
[E001] 无线耳机 ¥239.20 (¥299→¥239.20) | 保修:1年
----------------------
总计:¥287.50
"""

✨ 设计亮点:

  • 封装 :价格计算逻辑隐藏在final_price属性中
  • 继承:Book/Electronic复用Product基础功能
  • 多态:DiscountStrategy抽象类支持多种折扣算法无缝切换
  • 开闭原则:新增折扣类型无需修改购物车代码

五、常见误区与最佳实践

❌ 误区1:过度使用继承

python 复制代码
# 反面教材:继承层次过深
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class PetDog(Dog): pass
class Teddy(PetDog): pass  # 难以维护!

# 正确做法:优先组合
class Engine:
    def start(self): print("引擎启动")

class Car:
    def __init__(self):
        self.engine = Engine()  # 组合关系
    
    def start(self):
        self.engine.start()

❌ 误区2:滥用多继承

python 复制代码
# 反面教材:钻石继承导致方法冲突
class A:
    def method(self): print("A")

class B(A):
    def method(self): print("B")

class C(A):
    def method(self): print("C")

class D(B, C):  # MRO顺序:D→B→C→A
    pass

D().method()  # 输出"B",但逻辑可能不符合预期

✅ 最佳实践清单

场景 推荐方案 理由
代码复用 优先组合,其次单继承 组合更灵活,避免继承爆炸
接口统一 抽象基类 (ABC)或协议(Protocol) 明确契约,IDE友好
属性控制 @property替代getter/setter Pythonic,简洁优雅
扩展行为 策略模式替代条件分支 符合开闭原则

总结

今天我们系统掌握了OOP的三大核心特性:

  • 🔒 封装:用私有属性+property保护数据,暴露简洁接口
  • 🧬 继承 :通过super()实现代码复用,但警惕过度继承
  • 🦆 多态:基于抽象接口编写通用代码,拥抱鸭子类型

💡 三者关系:
封装 是基础(保护数据)→ 继承 是手段(复用代码)→ 多态是目标(灵活扩展)

它们共同支撑起Python生态中无数优秀框架的设计哲学。明天我们将进入Python标准库实战 ,教你用collectionsitertools写出更优雅高效的代码!


课后挑战

设计一个"动物园"系统:

  1. 定义抽象基类Animal,包含name属性和抽象方法make_sound()
  2. 创建DogCatBird子类,各自实现make_sound()
  3. 实现Zoo类,可添加动物并调用perform()让所有动物发声(多态应用)
  4. Bird添加私有属性__wing_span,并通过@property提供安全访问

提示:参考电商实战中的抽象基类+多态设计模式

相关推荐
寻寻觅觅☆10 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
YJlio11 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
l1t11 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿11 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar12312 小时前
C++使用format
开发语言·c++·算法
山塘小鱼儿12 小时前
本地Ollama+Agent+LangGraph+LangSmith运行
python·langchain·ollama·langgraph·langsimth
码说AI12 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS12 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
星空下的月光影子13 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言