从脚本到工程:掌握 Python 中的 OOP
Source: Dev.to
大多数开发者一开始会编写 过程式 代码。它就像食谱:一行行执行的指令列表。对于只有 10 行的脚本还能工作,但在大型系统中很快就会变得一团糟——这里改动一次可能会导致十处出错。
要真正理解 面向对象编程(OOP),请停止只考虑“代码”,而是开始从 现实世界 的角度思考。
在现实世界中,一切都是 对象——你的手机、你的汽车,甚至你的宠物猫。每个对象都有两件事:
- 它是什么(数据 / 属性) – 例如,颜色、重量、品牌。
- 它能做什么(行为 / 方法) – 例如,打电话、驾驶、喵叫。
这就像组建一支团队:各自专注的对象管理自己的数据和逻辑。
下面是使用真实购物应用对 OOP 的四大支柱 进行的拆解。
📦 1. 封装
概念
将数据和方法打包成一个单元,并 隐藏 那些混乱的内部实现。
现实类比
想象你的企业现金。在过程式代码中,钱散落在桌子上。而在面向对象编程中,它存放在 保险箱 中——你永远不直接接触现金,而是使用键盘(方法)与之交互。
过程式示例
# --- THE PROCEDURAL APPROACH ---
cart_items = []
def add_item(name, price):
cart_items.append({'name': name, 'price': price})
def get_total():
return sum(item['price'] for item in cart_items)
# Implementation
add_item("Mechanical Keyboard", 150.00)
add_item("Gaming Mouse", 80.00)
cart_items = [] # Oops! Someone reset the global variable accidentally.
print(f"Total: ${get_total()}") # Output: 0 (data lost!)
面向对象示例
# --- THE OOP APPROACH ---
class ShoppingCart:
def __init__(self):
# Private attribute: cannot be accessed directly from outside
self.__cart_items = []
def add_item(self, name: str, price: float):
self.__cart_items.append({"name": name, "price": price})
def get_total(self):
return sum(item['price'] for item in self.__cart_items)
# --- IMPLEMENTATION ---
cart = ShoppingCart()
cart.add_item("Mechanical Keyboard", 150.00)
cart.add_item("Gaming Mouse", 80.00)
# cart.__cart_items = [] # ERROR! This attribute is protected.
print(f"Total: ${cart.get_total()}") # Output: Total: $230.0
收获 – 你可以控制 数据 的修改方式。
🎭 2. 抽象与多态
概念
隐藏 实现方式,只暴露 功能。
现实类比
通用遥控器:你按下 电源,电视、音响和 DVD 播放器都会开启。你不关心每个设备是如何启动的,只在乎它们都能响应同一个指令。
过程式示例
# --- THE PROCEDURAL APPROACH ---
# Too many arguments to keep track of!
def apply_discount(total, type, value, voucher_code, voucher_exp, buy_X, get_Y):
# A massive if‑else block
if type == "percentage":
return total * (1 - value / 100)
elif type == "fixed":
return total - value
# Adding a new discount type means editing this function again.
# Implementation
print(apply_discount(100, "percentage", 10, '', '', 0, 0)) # 90.0
# The next call fails because of missing arguments:
# print(apply_discount(100, "buyXgetY", 10, '', 0)) # error
面向对象示例
# --- THE OOP APPROACH ---
from abc import ABC, abstractmethod
# --- ABSTRACTION: The Blueprint ---
class Discount(ABC):
@abstractmethod
def apply(self, total: float) -> float:
"""Return the discounted total."""
pass
# --- POLYMORPHISM: Flexible Implementations ---
class PercentageDiscount(Discount):
def __init__(self, percent: int):
self.percent = percent
def apply(self, total):
return total * (1 - self.percent / 100)
class VoucherDiscount(Discount):
def __init__(self, code: str, amount: float):
self.code = code
self.amount = amount
def apply(self, total):
print(f"--- Applying Voucher: {self.code} ---")
return total - self.amount
class BuyXGetYDiscount(Discount):
def __init__(self, x_qty: int, y_qty: int, unit_price: float):
self.x_qty = x_qty
self.y_qty = y_qty
self.unit_price = unit_price
def apply(self, total):
# Example logic: Get Y items free for every X items bought
free_items = self.y_qty
discount_value = free_items * self.unit_price
print(f"--- Buy {self.x_qty} Get {self.y_qty} Applied ---")
return total - discount_value
# --- IMPLEMENTATION (The code that NEVER changes) ---
def process_checkout(total, discount: Discount):
"""Calculate final price using the supplied discount strategy."""
final_price = discount.apply(total)
print(f"Final Price after discount: ${final_price}\n")
# Usage
process_checkout(200, PercentageDiscount(15)) # 15 % off
process_checkout(200, VoucherDiscount("SAVE50", 50.0)) # $50 voucher
process_checkout(200, BuyXGetYDiscount(2, 1, 20.0)) # Buy 2 get 1 ($20) free
收获 – 这遵循 开放‑封闭原则:你的代码对扩展开放(可以添加新折扣类型),但对修改封闭(process_checkout 再也不需要改动)。
Source: …
🧬 3. 继承
概念
基于已有类创建新类,这样就不必从头开始。
现实类比
想象一下 基础电器。每个电器都需要电源线和开/关开关。
- 烤面包机 继承了电源线,但额外添加了加热线圈。
- 搅拌机 继承了电源线,但额外添加了旋转刀片。
为什么? 你不必每次构建新厨房工具时都“重新发明”电和插头。只需继承基础功能,再加入自己的独特特性。
过程式: “复制‑粘贴” 噩梦
# --- THE PROCEDURAL APPROACH ---
def process_credit_card(user, amount, card_number, cvv):
# Repeated logic for every payment
if not user.get("is_logged_in", False):
return {"status": "failed", "reason": "User not logged in"}
if amount zero"}
status = "success" if len(card_number) == 16 and len(cvv) == 3 else "failed"
return {"type": "credit_card", "amount": amount, "status": status}
def process_paypal(user, amount, email):
# Same repeated checks
if not user.get("is_logged_in", False):
return {"status": "failed", "reason": "User not logged in"}
if amount zero"}
status = "success" if "@" in email else "failed"
return {"type": "paypal", "amount": amount, "status": status}
面向对象:继承 + 方法重写
# --- THE OOP APPROACH ---
from abc import ABC, abstractmethod
from typing import Dict
# --- PARENT CLASS ---
class Payment(ABC):
def __init__(self, user: dict, amount: float):
self.user = user
self.amount = amount
self.status = "pending"
self.reason = None
def _pre_check(self) -> bool:
"""Shared validation logic for all payments."""
if not self.user.get("is_logged_in", False):
self.status = "failed"
self.reason = "User not logged in"
return False
if self.amount bool:
"""Child implements payment‑specific validation."""
pass
def process(self) -> Dict[str, str]:
if not self._pre_check():
return {
"type": self.__class__.__name__,
"amount": self.amount,
"status": self.status,
"reason": self.reason,
}
self.status = "success" if self.validate() else "failed"
return {
"type": self.__class__.__name__,
"amount": self.amount,
"status": self.status,
}
# --- CHILD CLASSES ---
class CreditCardPayment(Payment):
def __init__(self, user: dict, amount: float, card_number: str, cvv: str):
super().__init__(user, amount)
self.card_number = card_number
self.cvv = cvv
def validate(self) -> bool:
return len(self.card_number) == 16 and len(self.cvv) == 3
class PayPalPayment(Payment):
def __init__(self, user: dict, amount: float, email: str):
super().__init__(user, amount)
self.email = email
def validate(self) -> bool:
return "@" in self.email
# --- USAGE ---
user = {"name": "Alice", "is_logged_in": True}
payments = [
CreditCardPayment(user, 100, "4111111111111111", "123"),
PayPalPayment(user, 0, "alice@example.com"), # Total=0 triggers pre‑check
]
results = [p.process() for p in payments]
# `results` will show status, including failed pre‑checks
收获
不要重复自己 (DRY)。 如果在 validate() 或 _pre_check() 中发现 bug,只需在父类里修复一次,所有子类都会受益。
⚖️ 结论
面向对象编程(OOP)不仅是一种编码风格——它是一种在软件中对现实世界问题进行建模的方法。通过关注具有数据(属性)和行为(方法)的对象,OOP 使开发者能够:
- 封装 数据并控制访问,防止意外修改。
- 抽象 复杂逻辑,提供清晰的接口,仅暴露必要的部分。
- 通过继承 复用 和扩展代码,减少重复并强制一致性。
- 实现 多态,使遵循相同契约的组件能够灵活、可互换。
与过程式编程相比,OOP 提升了 可维护性、可扩展性 和 可读性,让管理大型系统、添加新功能以及执行统一的业务规则变得更容易。掌握 OOP 能让开发者构建稳健、灵活且面向未来的软件,能够映射现实世界的复杂性。