2.4 Python 封装、继承、多态:面向对象的三大支柱
引言:构建灵活的代码架构
在上一节中,我们学习了如何使用构造方法、类方法和静态方法来构建类的骨架。现在,我们将深入探讨面向对象编程(OOP)最核心的三个概念:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这三者被称为OOP的三大支柱,它们共同作用,使我们能够构建出更加模块化、可复用和可维护的代码。
在我们的银行账户管理系统项目中,我们将看到:
- 封装 如何保护敏感的账户数据,只通过受控的接口进行访问。
- 继承 如何让我们创建不同类型的账户(如储蓄账户、支票账户),共享通用功能的同时拥有特定行为。
- 多态 如何让我们编写能够统一处理各种类型账户的通用代码。
让我们逐一深入探讨这三个强大的概念。
第一部分:封装(Encapsulation)—— 数据隐藏与接口暴露
封装是OOP中最基础的概念,它有两个主要目的:
- 将数据(属性)和操作数据的方法捆绑在一起,形成一个独立的单元(即类)。
- 隐藏对象的内部实现细节,仅对外暴露一个可控的访问接口。
1.1 访问控制:Public, Protected, Private
Python通过命名约定来实现访问控制,这是一种"约定大于强制"的哲学。
- Public(公开):普通命名(如
account_holder)。可以在任何地方被访问。 - Protected(保护):单下划线前缀(如
_balance)。提示"仅供内部使用",但依然可以被外部访问。这是一种温和的警告。 - Private(私有):双下划线前缀(如
__security_code)。会触发"名称修饰"(Name Mangling),使得在类外难以直接访问。
class BankAccount:
def __init__(self, account_holder, initial_balance=0, security_code):
self.account_holder = account_holder # Public 属性
self._balance = initial_balance # Protected 属性
self.__security_code = security_code # Private 属性
# Public 方法:存款接口
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
# Public 方法:取款接口(需要验证安全码)
def withdraw(self, amount, security_code):
if self.__verify_security_code(security_code) and amount <= self._balance:
self._balance -= amount
return True
return False
# Protected 方法:子类可能需要访问余额
def _get_balance(self):
return self._balance
# Private 方法:验证安全码
def __verify_security_code(self, input_code):
return self.__security_code == input_code
# 使用示例
account = BankAccount("Alice", 1000, "1234")
print(account.account_holder) # 可以直接访问:Alice
print(account._balance) # 可以访问但不建议:1000
# print(account.__security_code) # 错误:无法直接访问私有属性
# print(account.__verify_security_code("1234")) # 错误:无法直接访问私有方法
# 通过公共接口操作账户
account.deposit(500)
success = account.withdraw(200, "1234")
print(success) # 输出:True
print(account._get_balance()) # 输出:1300
名称修饰(Name Mangling)揭秘:Python会将 __security_code 重命名为 _BankAccount__security_code。理论上你可以通过 account._BankAccount__security_code 访问它,但这绝对是不好的做法。
1.2 属性装饰器:更优雅的封装
对于需要更精细控制的属性,Python提供了属性装饰器(@property, @<attribute>.setter)。
class BankAccount:
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder
self._balance = initial_balance # 保护属性,实际存储值
@property
def balance(self):
"""余额的只读属性"""
print("余额被查询")
return self._balance
@property
def is_overdrawn(self):
"""只读属性:检查是否透支"""
return self._balance < 0
@property
def account_info(self):
"""只读属性:返回账户信息摘要"""
return f"持有人: {self.account_holder}, 余额: ${self._balance}"
# 没有为balance提供setter,所以外部不能直接设置balance
# 使用
account = BankAccount("Bob", 500)
print(account.balance) # 输出:余额被查询 → 500
print(account.is_overdrawn) # 输出:False
print(account.account_info) # 输出:持有人: Bob, 余额: $500
# account.balance = 1000 # 错误:无法设置属性
1.3 封装的优点
- 提高安全性:防止意外修改敏感数据
- 降低耦合度:内部实现可以自由更改而不影响外部代码
- 简化接口:使用者只需要知道如何使用公共方法,不需要了解内部细节
第二部分:继承(Inheritance)—— 代码复用与层次化设计
继承允许我们基于现有类创建新类,新类将自动获得父类的所有属性和方法,并可以添加或重写功能。
2.1 基本语法
class ParentClass:
# 父类的属性和方法
pass
class ChildClass(ParentClass): # 括号中指定父类
# 可以添加新属性和方法,或重写父类的方法
pass
2.2 实战:银行账户的继承体系
让我们创建几种特定类型的银行账户:
# 基础账户类
class BankAccount:
interest_rate = 0.02 # 类属性:基础利率
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder
self._balance = initial_balance
self.account_number = self._generate_account_number()
def _generate_account_number(self):
return f"ACC{random.randint(100000, 999999)}"
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
def withdraw(self, amount):
if amount > 0 and amount <= self._balance:
self._balance -= amount
return True
return False
def get_balance(self):
return self._balance
def calculate_interest(self):
return self._balance * self.interest_rate
# 储蓄账户继承自基础账户
class SavingsAccount(BankAccount):
interest_rate = 0.04 # 重写类属性:更高的利率
def __init__(self, account_holder, initial_balance=0):
super().__init__(account_holder, initial_balance) # 调用父类构造方法
self.min_balance = 100 # 储蓄账户要求最低余额
# 重写取款方法,添加最低余额检查
def withdraw(self, amount):
if self._balance - amount >= self.min_balance:
return super().withdraw(amount) # 调用父类方法
print(f取款失败。必须保持最低余额 ${self.min_balance}")
return False
# 支票账户继承自基础账户
class CheckingAccount(BankAccount):
def __init__(self, account_holder, initial_balance=0, overdraft_limit=0):
super().__init__(account_holder, initial_balance)
self.overdraft_limit = overdraft_limit # 透支额度
# 重写取款方法,支持透支
def withdraw(self, amount):
if amount > 0 and amount <= (self._balance + self.overdraft_limit):
self._balance -= amount
return True
return False
# 高级账户继承自储蓄账户
class PremiumAccount(SavingsAccount):
interest_rate = 0.06 # 更高的利率
def __init__(self, account_holder, initial_balance=0):
super().__init__(account_holder, initial_balance)
self.reward_points = 0 # 奖励点数
# 扩展存款方法,增加奖励点数
def deposit(self, amount):
if super().deposit(amount):
self.reward_points += amount * 0.01 # 每存$1得0.01点
return True
return False
关键点解析:
super():用于调用父类的方法,特别是在重写方法时保留父类的功能。- 方法重写(Override):子类可以重新定义父类的方法,提供特定的实现。
- 继承链:
PremiumAccount继承自SavingsAccount,而SavingsAccount又继承自BankAccount,形成了多级继承。
2.3 检查继承关系
Python提供了内置函数来检查继承关系:
# 创建实例
savings = SavingsAccount("Alice", 1000)
checking = CheckingAccount("Bob", 500, 200)
# 检查类型和继承关系
print(isinstance(savings, SavingsAccount)) # True
print(isinstance(savings, BankAccount)) # True (因为SavingsAccount继承自BankAccount)
print(issubclass(CheckingAccount, BankAccount)) # True
print(issubclass(PremiumAccount, SavingsAccount)) # True
第三部分:多态(Polymorphism)—— 同一接口,多种实现
多态意为"多种形态"。在OOP中,它指的是:不同的对象可以对同一消息(方法调用)做出不同的响应。
3.1 多态的工作原理
多态的实现依赖于两个前提:
- 继承关系
- 方法重写
# 创建一个处理各种账户的函数
def process_account_interest(account):
"""处理账户利息,适用于任何BankAccount及其子类"""
interest = account.calculate_interest() # 多态调用
account.deposit(interest)
print(f"{account.account_holder} 的账户获得利息: ${interest:.2f}")
# 创建不同类型的账户
accounts = [
BankAccount("普通用户", 1000),
SavingsAccount("储蓄用户", 1000),
PremiumAccount("高级用户", 1000)
]
# 统一处理所有账户,但每个账户的行为不同
for account in accounts:
process_account_interest(account)
输出可能是:
普通用户 的账户获得利息: $20.00
储蓄用户 的账户获得利息: $40.00
高级用户 的账户获得利息: $60.00
3.2 抽象基类(ABC)与多态
为了确保子类实现了必要的方法,我们可以使用抽象基类:
from abc import ABC, abstractmethod
class BankAccount(ABC):
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder
self._balance = initial_balance
@abstractmethod
def calculate_monthly_fee(self):
"""计算月度费用,子类必须实现"""
pass
# 其他具体方法...
3.3 鸭式辨型(Duck Typing)
Python的多态更加灵活,它不要求严格的继承关系,只关心对象是否具有所需的方法(“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”)。
class CryptoWallet:
"""一个完全不同的类,但碰巧也有calculate_interest方法"""
def __init__(self, owner, amount):
self.owner = owner
self.amount = amount
def calculate_interest(self):
return self.amount * 0.08 # 加密货币的高利息
# 这个对象也可以传给process_account_interest函数!
wallet = CryptoWallet("加密用户", 1000)
process_account_interest(wallet) # 也能工作!
第四部分:综合实战 —— 完整的银行账户管理系统
现在,让我们将封装、继承和多态综合应用到一个完整的银行账户系统中:
from abc import ABC, abstractmethod
import datetime
import random
class BankAccount(ABC):
# 类属性
total_accounts = 0
_next_id = 1000
def __init__(self, account_holder, initial_balance=0):
# 输入验证
if not isinstance(account_holder, str) or not account_holder.strip():
raise ValueError("账户持有人必须是非空字符串")
if initial_balance < 0:
raise ValueError("初始余额不能为负数")
# 属性初始化
self._account_holder = account_holder
self._balance = initial_balance
self._account_id = BankAccount._generate_account_id()
self._created_date = datetime.datetime.now()
self._transactions = []
BankAccount.total_accounts += 1
@classmethod
def _generate_account_id(cls):
account_id = cls._next_id
cls._next_id += 1
return account_id
# 属性访问器
@property
def account_holder(self):
return self._account_holder
@property
def balance(self):
return self._balance
@property
def account_id(self):
return self._account_id
@property
def account_info(self):
return f"{self._account_id}: {self._account_holder} - ${self._balance:.2f}"
# 公共方法
def deposit(self, amount, description="存款"):
if amount <= 0:
raise ValueError("存款金额必须为正数")
self._balance += amount
self._record_transaction(amount, description)
return True
def withdraw(self, amount, description="取款"):
if amount <= 0:
raise ValueError("取款金额必须为正数")
if not self._can_withdraw(amount):
return False
self._balance -= amount
self._record_transaction(-amount, description)
return True
def get_transaction_history(self):
return self._transactions.copy()
# 抽象方法
@abstractmethod
def _can_withdraw(self, amount):
"""检查是否可以取款,子类必须实现"""
pass
@abstractmethod
def calculate_monthly_fee(self):
"""计算月度费用,子类必须实现"""
pass
# 保护方法
def _record_transaction(self, amount, description):
transaction = {
"date": datetime.datetime.now(),
"amount": amount,
"description": description,
"balance_after": self._balance
}
self._transactions.append(transaction)
# 魔术方法
def __str__(self):
return self.account_info
class SavingsAccount(BankAccount):
def __init__(self, account_holder, initial_balance=0):
super().__init__(account_holder, initial_balance)
self._min_balance = 50 # 最低余额要求
def _can_withdraw(self, amount):
return self._balance - amount >= self._min_balance
def calculate_monthly_fee(self):
# 储蓄账户:如果余额低于100,收取2美元费用
return 2.0 if self._balance < 100 else 0.0
def apply_interest(self, rate=0.04):
"""储蓄账户特有方法:应用利息"""
interest = self._balance * rate
self.deposit(interest, "利息收入")
return interest
class CheckingAccount(BankAccount):
def __init__(self, account_holder, initial_balance=0, overdraft_limit=100):
super().__init__(account_holder, initial_balance)
self._overdraft_limit = overdraft_limit
def _can_withdraw(self, amount):
return self._balance + self._overdraft_limit >= amount
def calculate_monthly_fee(self):
# 支票账户:固定月费5美元,但如果余额超过1000则免收
return 5.0 if self._balance < 1000 else 0.0
class Bank:
"""银行类,管理所有账户"""
def __init__(self, name):
self.name = name
self._accounts = []
def add_account(self, account):
if not isinstance(account, BankAccount):
raise TypeError("只能添加BankAccount对象")
self._accounts.append(account)
def total_deposits(self):
return sum(account.balance for account in self._accounts)
def apply_monthly_fees(self):
"""为所有账户应用月度费用"""
total_fees = 0
for account in self._accounts:
fee = account.calculate_monthly_fee()
if fee > 0:
account.withdraw(fee, "月度费用")
total_fees += fee
return total_fees
def find_account(self, account_id):
"""根据ID查找账户"""
for account in self._accounts:
if account.account_id == account_id:
return account
return None
# 演示使用
if __name__ == "__main__":
# 创建银行
my_bank = Bank("Python银行")
# 创建各种账户
savings1 = SavingsAccount("Alice", 500)
checking1 = CheckingAccount("Bob", 300, 200)
savings2 = SavingsAccount("Charlie", 1500)
# 将账户添加到银行
my_bank.add_account(savings1)
my_bank.add_account(checking1)
my_bank.add_account(savings2)
# 进行一些交易
savings1.deposit(200, "工资")
checking1.withdraw(400, "房租") # 使用透支
savings2.withdraw(100, "购物")
# 应用月度费用
total_fees = my_bank.apply_monthly_fees()
print(f"本月共收取费用: ${total_fees:.2f}")
# 显示所有账户信息
print(f"\n{my_bank.name} 账户总览:")
for account in my_bank._accounts:
print(f" {account}")
# 显示总存款
print(f"\n银行总存款: ${my_bank.total_deposits():.2f}")
设计亮点:
- 完整的封装:所有敏感数据都被保护,只能通过受控的方法访问。
- 清晰的继承体系:
SavingsAccount和CheckingAccount继承自抽象基类BankAccount,各自实现了特定的行为。 - 多态的应用:
Bank类可以统一处理各种类型的账户,无需关心具体是哪种账户。 - 抽象基类:确保所有账户类型都实现了必要的方法。
第五部分:常见面试题深度解析
-
Q:Python中如何实现真正的私有属性?
A:Python中没有真正的"私有"概念,但通过双下划线触发名称修饰是最接近的实现。这是一种约定,而不是强制限制,旨在防止意外访问而非恶意访问。 -
Q:
super()在多重继承中如何工作?
A:super()遵循方法解析顺序(MRO),使用C3线性化算法确定调用顺序。你可以通过ClassName.__mro__查看MRO顺序。 -
Q:什么时候应该使用抽象基类?
A:当你需要确保一组相关类实现特定接口时,使用抽象基类。它定义了子类必须实现的抽象方法,强制接口一致性。 -
Q:多态与 duck typing 有什么区别?
A:多态通常基于继承关系,而 duck typing 基于对象的能力(具有所需方法)。Duck typing 更加灵活,但不提供编译时类型检查。
结语与思考
封装、继承和多态是面向对象编程的三大支柱,它们共同使我们能够构建出灵活、可维护和可扩展的代码系统。
在我们的银行账户项目实战中,你看到了:
- 封装 如何保护核心数据并提供清晰的接口
- 继承 如何创建层次化的账户体系,实现代码复用
- 多态 如何让银行系统能够统一处理各种类型的账户
这些概念的理解和运用,是成为一名高级Python开发者的关键。
思考题:
- 如果我们要添加一个
BusinessAccount(商业账户)类,它应该继承自哪个类?它可能有哪些特有的属性和方法? - 如何改进当前的交易记录系统,使其支持按时间范围查询交易记录?
- 如果银行需要支持国际账户(不同货币),应该如何修改当前的设计?
更多推荐

所有评论(0)