在掌握了 OOP 三大核心特性(封装、继承、多态)后,我们需要进一步学习 Python 中类的进阶方法(类方法、静态方法)和实用设计模式。类方法和静态方法拓展了类的功能边界,分别用于 “操作类属性” 和 “提供工具函数”;而设计模式则是 OOP 思想的经典实践,能帮助我们解决复杂场景下的代码设计问题。本章将从类方法、静态方法的语法与应用,到单例模式、工厂模式的实现,逐步提升你的 OOP 实战能力。
7.1 类方法(@classmethod)
在之前的学习中,我们接触的主要是 “实例方法”—— 依赖于对象,第一个参数必须是self,用于操作实例属性。而类方法是依赖于 “类” 的方法,通过@classmethod装饰器定义,第一个参数是cls(代表当前类,是 Python 的约定,不可省略),主要用于操作类属性或创建类的实例。
7.1.1 类方法的定义与调用
定义语法
class 类名:
# 类属性(类方法通常操作类属性)
类属性名 = 初始值
# 类方法:用@classmethod装饰,第一个参数是cls
@classmethod
def 类方法名(cls, 参数1, 参数2, ...):
# 方法逻辑:可通过cls访问类属性或调用其他类方法
cls.类属性名 = 新值 # 修改类属性
return 结果
语法说明
- @classmethod装饰器:必须放在类方法定义的上方,用于标识该方法是类方法;
- cls参数:代表当前类(如Student类的类方法中,cls就是Student类本身),通过cls可访问类属性、调用其他类方法,或创建类的实例;
- 调用方式:既可以通过 “类名.类方法名(参数)” 调用(推荐),也可以通过 “对象.类方法名(参数)” 调用(不推荐,易混淆实例方法)。
示例:类方法操作类属性
假设我们需要统计Student类创建的实例数量,可通过类属性count记录,并用类方法get_instance_count获取统计结果:
class Student:
# 类属性:统计实例数量,初始值为0
count = 0
def __init__(self, name, age):
self.name = name
self.age = age
# 每次创建实例,类属性count加1(通过类名访问类属性)
Student.count += 1
# 类方法:获取实例数量(操作类属性count)
@classmethod
def get_instance_count(cls):
# 通过cls访问类属性(cls等价于Student)
return f"Student类共创建了{cls.count}个实例"
# 类方法:修改类属性(批量更新类级别的配置)
@classmethod
def reset_count(cls):
cls.count = 0
return "实例计数器已重置为0"
# 1. 创建3个Student实例
student1 = Student("小明", 20)
student2 = Student("小红", 19)
student3 = Student("小刚", 21)
# 2. 通过“类名”调用类方法(推荐)
print(
Student.get_instance_count()) # 输出:Student类共创建了3个实例
# 3. 通过“对象”调用类方法(不推荐,语法允许但逻辑上不符合类方法的设计意图)
print(
student1.get_instance_count()) # 输出:Student类共创建了3个实例
# 4. 调用类方法重置计数器
print(Student.reset_count()) # 输出:实例计数器已重置为0
print(
Student.get_instance_count()) # 输出:Student类共创建了0个实例
运行结果:
Student类共创建了3个实例
Student类共创建了3个实例
实例计数器已重置为0
Student类共创建了0个实例
类方法的核心作用:
- 操作类属性:类方法是修改和访问类属性的 “安全接口”,避免外部直接修改类属性(类似实例方法对实例属性的封装);
- 类级别的工具函数:实现与类相关但不依赖实例的逻辑(如统计实例数量、批量更新类配置)。
7.1.2 类方法的进阶应用:创建类的实例
类方法的另一个重要用途是 “创建类的实例”—— 当实例化过程需要复杂的逻辑(如从配置文件、数据库或 JSON 字符串中加载数据)时,可通过类方法封装实例化逻辑,对外提供简洁的调用接口(这种方式也称为 “工厂方法” 的简化版)。
示例:类方法从 JSON 字符串创建实例
import json
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def __str__(self):
return f"Student(name='{self.name}', age={self.age}, score={self.score})"
# 类方法:从JSON字符串创建Student实例(封装实例化逻辑)
@classmethod
def from_json(cls, json_str):
# 1. 解析JSON字符串为字典
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"JSON解析失败:{e}")
# 2. 校验字典中是否包含必要的键
required_keys = ["name", "age", "score"]
if not all(key in data for key in required_keys):
raise KeyError(f"JSON数据缺少必要字段,需包含{required_keys}")
# 3. 创建并返回实例(cls等价于Student,cls(**data)等价于Student(**data))
return cls(** data)
# 1. 定义JSON字符串(模拟从文件或网络获取的数据)
json_data = '''
{
"name": "小明",
"age": 20,
"score": 95.5
}
'''
# 2. 通过类方法创建实例(无需手动解析JSON和校验数据)
try:
student1 = Student.from_json(json_data)
print("从JSON创建的实例:", student1) # 输出:从JSON创建的实例:Student(name='小明', age=20, score=95.5)
except (ValueError, KeyError) as e:
print("创建实例失败:", e)
# 3. 尝试用非法JSON创建实例(触发异常)
invalid_json = '{"name": "小红", "age": 19}' # 缺少score字段
try:
student2 = Student.from_json(invalid_json)
except KeyError as e:
print("创建实例失败:", e) # 输出:创建实例失败:JSON数据缺少必要字段,需包含['name', 'age', 'score']
运行结果:
从JSON创建的实例: Student(name='小明', age=20, score=95.5)
创建实例失败: JSON数据缺少必要字段,需包含['name', 'age', 'score']
优势分析:
- 逻辑封装:将 “JSON 解析→数据校验→实例创建” 的复杂逻辑封装在类方法中,外部调用者只需传入 JSON 字符串,无需关心内部细节;
- 可扩展性:若后续需要从 “CSV 文件” 或 “数据库” 创建实例,只需新增from_csv、from_db等类方法,接口风格统一,易于维护;
- 代码复用:多个地方需要从 JSON 创建实例时,无需重复编写解析和校验逻辑,直接调用from_json即可。
7.2 静态方法(@staticmethod)
静态方法是类中与 “类” 和 “实例” 都无关的方法,通过@staticmethod装饰器定义,没有默认参数(既无self也无cls)。它本质是一个 “普通函数”,只是被定义在类的命名空间下,用于提供与类相关但不依赖类属性或实例属性的工具函数。
7.2.1 静态方法的定义与调用
定义语法
class 类名:
# 静态方法:用@staticmethod装饰,无默认参数
@staticmethod
def 静态方法名(参数1, 参数2, ...):
# 方法逻辑:不访问类属性和实例属性,仅依赖传入的参数
pass
语法说明
- @staticmethod装饰器:必须放在静态方法定义的上方,用于标识该方法是静态方法;
- 无默认参数:静态方法不依赖self(实例)或cls(类),因此没有默认参数,参数完全由调用者传入;
- 调用方式:既可以通过 “类名.静态方法名(参数)” 调用(推荐),也可以通过 “对象.静态方法名(参数)” 调用(不推荐)。
示例:静态方法实现工具函数
假设Student类需要一个 “验证手机号格式” 的工具函数 —— 该函数与具体的学生实例无关(不依赖name、age等属性),也与Student类的类属性无关,适合定义为静态方法:
import re
class Student:
def __init__(self, name, age, phone):
# 调用静态方法验证手机号格式
if not Student.is_valid_phone(phone):
raise ValueError("手机号格式无效(需为11位数字)")
self.name = name
self.age = age
self.phone = phone
# 静态方法:验证手机号格式(工具函数,与类/实例无关)
@staticmethod
def is_valid_phone(phone):
# 正则表达式:匹配11位数字
pattern = r'^1\d{10}#39;
return isinstance(phone, str) and re.match(pattern, phone) is not None
# 1. 通过“类名”调用静态方法(推荐,直接作为工具函数使用)
print("验证手机号13800138000:", Student.is_valid_phone("13800138000")) # 输出:验证手机号13800138000:True
print("验证手机号123456:", Student.is_valid_phone("123456")) # 输出:验证手机号123456:False
# 2. 创建实例(手机号合法)
try:
student1 = Student("小明", 20, "13800138000")
print(f"创建成功:{student1.name},手机号:{student1.phone}") # 输出:创建成功:小明,手机号:13800138000
except ValueError as e:
print("创建失败:", e)
# 3. 创建实例(手机号非法)
try:
student2 = Student("小红", 19, "123456")
except ValueError as e:
print("创建失败:", e) # 输出:创建失败:手机号格式无效(需为11位数字)
运行结果:
验证手机号13800138000: True
验证手机号123456: False
创建成功:小明,手机号:13800138000
创建失败:手机号格式无效(需为11位数字)
静态方法的核心作用:
- 提供工具函数:封装与类相关但不依赖类 / 实例属性的通用逻辑(如格式验证、数据转换、计算等);
- 避免命名污染:将工具函数定义在类的命名空间下,避免与全局函数重名(如is_valid_phone属于Student类,不会与其他类的is_valid_phone冲突)。
7.2.2 类方法、静态方法与实例方法的区别
为了清晰区分三种方法的适用场景,我们从 “参数、依赖对象、访问权限、调用方式” 四个维度进行对比:
对比维度 | 实例方法(Instance Method) | 类方法(Class Method) | 静态方法(Static Method) |
第一个参数 | self(代表当前实例) | cls(代表当前类) | 无默认参数 |
依赖对象 | 依赖实例(必须通过实例调用,或手动传实例) | 依赖类(无需实例,通过类调用) | 不依赖类和实例(纯函数) |
访问权限 | 可访问实例属性和类属性 | 可访问类属性,不可访问实例属性 | 不可访问实例属性和类属性(仅用传入参数) |
调用方式 | 对象.方法名() 或 类名.方法名(实例) | 类名.方法名() 或 对象.方法名() | 类名.方法名() 或 对象.方法名() |
核心用途 | 操作实例属性,实现对象的行为 | 操作类属性,创建实例,类级工具逻辑 | 提供与类相关的纯工具函数 |
示例:三种方法的对比实现
class Demo:
# 类属性
class_attr = "类属性值"
def __init__(self, instance_attr):
# 实例属性
self.instance_attr = instance_attr
# 1. 实例方法:依赖self,访问实例属性和类属性
def instance_method(self):
print(f"实例方法 - 实例属性:{self.instance_attr},类属性:{self.class_attr}")
# 2. 类方法:依赖cls,访问类属性,不可访问实例属性
@classmethod
def class_method(cls):
print(f"类方法 - 类属性:{cls.class_attr}")
# 错误:类方法无法访问实例属性(cls没有instance_attr)
# print(f"实例属性:{cls.instance_attr}")
# 3. 静态方法:无默认参数,不访问类属性和实例属性
@staticmethod
def static_method(a, b):
print(f"静态方法 - 传入参数的和:{a + b}")
# 错误:静态方法无法访问类属性和实例属性
# print(f"类属性:{cls.class_attr}")
# print(f"实例属性:{self.instance_attr}")
# 创建实例
demo = Demo("实例属性值")
# 调用三种方法
print("=== 调用实例方法 ===")
demo.instance_method() # 输出:实例方法 - 实例属性:实例属性值,类属性:类属性值
print("\n=== 调用类方法 ===")
Demo.class_method() # 输出:类方法 - 类属性:类属性值
print("\n=== 调用静态方法 ===")
Demo.static_method(3, 5) # 输出:静态方法 - 传入参数的和:8
运行结果:
=== 调用实例方法 ===
实例方法 - 实例属性:实例属性值,类属性:类属性值
=== 调用类方法 ===
类方法 - 类属性:类属性值
=== 调用静态方法 ===
静态方法 - 传入参数的和:8
7.2.3 方法选择的实用建议
在实际开发中,选择哪种方法需根据 “是否依赖类 / 实例属性” 来判断,具体建议如下:
- 选择实例方法:
- 方法需要访问或修改实例属性(如Student的get_score、set_score);
- 方法是对象的 “行为”(如Dog的bark、Student的study)。
- 选择类方法:
- 方法需要访问或修改类属性(如统计实例数量、更新类配置);
- 方法需要创建类的实例(如从 JSON、CSV 加载数据创建实例);
- 方法的逻辑与 “类” 相关,与 “具体实例” 无关(如Math类的sum方法,计算多个数字的和)。
- 选择静态方法:
- 方法是纯工具函数,仅依赖传入的参数,不访问类属性和实例属性(如格式验证、数据转换);
- 方法与类相关,但不需要与类或实例绑定(如Date类的is_leap_year方法,判断某一年是否为闰年)。
7.3 OOP 设计模式入门(实用篇)
设计模式是 OOP 开发中总结的 “可复用解决方案”,用于解决特定场景下的代码设计问题(如 “确保一个类只有一个实例”“统一创建不同子类的对象”)
。掌握设计模式能让你的代码更具可读性、可维护性和扩展性,避免重复 “造轮子”。本节将介绍两种最常用的设计模式:单例模式(确保类只有一个实例)和工厂模式(统一创建子类对象),并结合 Python 特性讲解实现方式。
7.3.1 单例模式:保证一个类只有一个实例
在某些场景下,我们需要确保一个类只能创建一个实例(如数据库连接池、配置管理器、日志对象)—— 若创建多个实例,可能导致资源冲突(如多个数据库连接修改同一数据)或资源浪费(如重复创建重量级对象)。单例模式(Singleton Pattern)就是解决这类问题的经典方案。
单例模式的核心思想
- 私有构造:通过私有化__init__方法或__new__方法(Python 中创建对象的底层方法),阻止外部直接实例化;
- 全局访问点:提供一个静态方法或类方法,作为获取唯一实例的全局接口,确保每次调用都返回同一个实例。
Python 中实现单例模式的 3 种常用方式
方式 1:基于__new__方法(推荐,最简洁)
__new__是 Python 中比__init__更早执行的方法,负责 “创建对象并分配内存”。通过重写__new__方法,控制对象的创建逻辑,确保只生成一个实例。
class Singleton:
# 类属性:存储唯一实例
_instance = None
def __new__(cls, *args, **kwargs):
# 1. 判断是否已创建实例:若未创建,调用父类__new__创建;若已创建,直接返回
if cls._instance is None:
# 调用object的__new__方法,创建对象并分配内存
cls._instance = super().__new__(cls)
# 2. 返回唯一实例(无论是否新创建)
return cls._instance
def __init__(self, name):
# 注意:若多次调用__init__,会重复初始化实例属性(需处理)
if not hasattr(self, "name"): # 仅当实例未初始化name时,才赋值
self.name = name
# 测试单例模式:多次创建对象,判断是否为同一个实例
s1 = Singleton("实例1")
s2 = Singleton("实例2")
# 1. 判断两个对象的内存地址是否相同(相同则为同一实例)
print(f"s1的内存地址:{id(s1)}") # 输出:s1的内存地址:2520858445648(示例值)
print(f"s2的内存地址:{id(s2)}") # 输出:s2的内存地址:2520858445648(与s1相同)
# 2. 查看实例属性(s2的name未覆盖s1的name,因hasattr判断避免重复初始化)
print(f"s1.name:{s1.name}") # 输出:s1.name:实例1
print(f"s2.name:{s2.name}") # 输出:s2.name:实例1
# 3. 验证是否为同一实例(is判断内存地址)
print(f"s1 is s2:{s1 is s2}") # 输出:s1 is s2:True
运行结果:
s1的内存地址:2520858445648
s2的内存地址:2520858445648
s1.name:实例1
s2.name:实例1
s1 is s2:True
关键逻辑:
- _instance类属性存储唯一实例,初始值为None;
- 每次调用Singleton()时,先执行__new__:若_instance为None,创建新实例并赋值给_instance;否则直接返回_instance;
- __init__中通过hasattr(self, "name")判断,避免多次调用时重复初始化实例属性。
方式 2:基于装饰器(灵活,可复用)
通过装饰器包装类,在装饰器内部维护唯一实例,实现单例逻辑。这种方式的优势是 “解耦”—— 单例逻辑与类本身分离,可复用给多个类。
def singleton(cls):
# 字典:存储被装饰类的唯一实例(key:类,value:实例)
instances = {}
def wrapper(*args, **kwargs):
# 1. 若类未在instances中,创建实例并加入;否则直接返回已有实例
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
# 用装饰器将普通类变为单例类
@singleton
class DatabaseConnection:
def __init__(self, host, port):
self.host = host
self.port = port
def connect(self):
print(f"连接数据库:{self.host}:{self.port}")
# 测试单例模式
db1 = DatabaseConnection("localhost", 3306)
db2 = DatabaseConnection("127.0.0.1", 3306) # 传入不同参数,但仍返回同一实例
print(f"db1 is db2:{db1 is db2}") # 输出:db1 is db2:True
db1.connect() # 输出:连接数据库:localhost:3306(db2的参数未生效,因实例已创建)
db2.connect() # 输出:连接数据库:localhost:3306
运行结果:
db1 is db2:True
连接数据库:localhost:3306
连接数据库:localhost:3306
优势:
- 灵活性高:只需给类添加@singleton装饰器,即可实现单例,无需修改类内部代码;
- 可复用性:同一个装饰器可用于多个类(如@singleton也可装饰ConfigManager类)。
方式 3:基于模块(Python 特有,最简单)
Python 的模块具有 “导入时只加载一次” 的特性 —— 当一个模块被多次导入时,Python 只会创建一个模块对象,不会重复加载。利用这一特性,可将单例实例定义在模块中,实现单例模式。
步骤:
- 创建singleton_module.py模块,在模块中创建类的实例;
- 其他文件通过from singleton_module import 实例名导入,多次导入的都是同一个实例。
示例:
- singleton_module.py(模块文件):
class ConfigManager:
def __init__(self):
# 模拟加载配置文件
self.config = {"debug": True, "timeout": 30}
def get_config(self, key):
return self.config.get(key)
# 模块中创建唯一实例
config_manager = ConfigManager()
- 主程序文件:
# 第一次导入实例
from singleton_module import config_manager as cm1
# 第二次导入实例(与cm1是同一个)
from singleton_module import config_manager as cm2
# 验证是否为同一实例
print(f"cm1 is cm2:{cm1 is cm2}") # 输出:cm1 is cm2:True
# 调用实例方法(结果一致)
print(f"cm1获取debug配置:{cm1.get_config('debug')}") # 输出:cm1获取debug配置:True
print(f"cm2获取timeout配置:{cm2.get_config('timeout')}") # 输出:cm2获取timeout配置:30
运行结果:
cm1 is cm2:True
cm1获取debug配置:True
cm2获取timeout配置:30
优势:
- 最简单:无需重写__new__或使用装饰器,利用 Python 模块特性即可实现;
- 无副作用:不会修改类的原有逻辑,兼容性好。
单例模式的适用场景
- 资源密集型对象:如数据库连接、网络连接、线程池等,创建成本高,重复创建会浪费资源;
- 全局配置对象:如配置管理器,整个系统只需一个配置实例,确保所有模块使用统一配置;
- 日志对象:日志写入需保证顺序,多个日志实例可能导致日志内容混乱;
- 工具类对象:如日期工具、加密工具等,无需多个实例,全局共享一个即可。
7.3.2 工厂模式:通过 “工厂类” 统一创建不同子类对象
在多继承或子类较多的场景下,直接通过子类名()创建对象会导致 “创建逻辑分散”—— 若后续子类名修改或新增子类,需修改所有创建对象的代码。工厂模式(Factory Pattern)通过定义一个 “工厂类”,统一负责所有子类对象的创建,外部只需告诉工厂 “要创建什么类型的对象”,无需关心具体创建逻辑。
工厂模式的核心思想
- 抽象产品:定义所有产品(子类)的统一接口(如Payment抽象类,包含pay方法);
- 具体产品:实现抽象产品接口的子类(如WeChatPayment、AlipayPayment);
- 工厂类:提供静态方法或类方法,根据传入的参数,创建并返回对应的具体产品实例(如PaymentFactory.create_payment("wechat")返回WeChatPayment实例)。
Python 中实现工厂模式(以 “支付方式” 为例)
假设我们需要开发一个支付系统,支持微信支付、支付宝支付、银联支付三种方式,每种支付方式的逻辑不同,但都需要 “发起支付” 的接口。通过工厂模式,可统一管理支付方式的创建逻辑。
步骤 1:定义抽象产品(Payment 抽象类)
from abc import ABC, abstractmethod
# 抽象产品:支付方式接口
class Payment(ABC):
@abstractmethod
def pay(self, amount):
"""抽象方法:发起支付,子类必须重写"""
pass
步骤 2:定义具体产品(不同支付方式的子类)
# 具体产品1:微信支付
class WeChatPayment(Payment):
def pay(self, amount):
return f"微信支付成功:{amount}元(订单号:WX{hash(self)}{amount})"
# 具体产品2:支付宝支付
class AlipayPayment(Payment):
def pay(self, amount):
return f"支付宝支付成功:{amount}元(订单号:ALIPAY{hash(self)}{amount})"
# 具体产品3:银联支付
class UnionPayPayment(Payment):
def pay(self, amount):
return f"银联支付成功:{amount}元(订单号:UNION{hash(self)}{amount})"
步骤 3:定义工厂类(PaymentFactory)
class PaymentFactory:
# 静态方法:根据支付类型创建对应的支付实例
@staticmethod
def create_payment(payment_type):
# 统一创建逻辑:根据参数返回不同子类实例
if payment_type.lower() == "wechat":
return WeChatPayment()
elif payment_type.lower() == "alipay":
return AlipayPayment()
elif payment_type.lower() == "unionpay":
return UnionPayPayment()
else:
raise ValueError(f"不支持的支付方式:{payment_type},支持的类型:wechat/alipay/unionpay")
步骤 4:使用工厂模式创建对象
# 1. 通过工厂类创建不同支付方式的实例(无需直接调用子类构造函数)
try:
# 创建微信支付实例
wechat_pay = PaymentFactory.create_payment("wechat")
# 创建支付宝支付实例
alipay_pay = PaymentFactory.create_payment("alipay")
# 创建银联支付实例
unionpay_pay = PaymentFactory.create_payment("unionpay")
# 2. 调用支付方法(多态体现:统一接口,不同实现)
print(wechat_pay.pay(100)) # 输出:微信支付成功:100元(订单号:WX1407152043190881024100)
print(alipay_pay.pay(200)) # 输出:支付宝支付成功:200元(订单号:
ALIPAY1407152043190881088200)
print(unionpay_pay.pay(300)) # 输出:银联支付成功:300元(订单号:
UNION1407152043190881152300)
# 3. 尝试创建不支持的支付方式(触发异常)
invalid_pay = PaymentFactory.create_payment("applepay")
except ValueError as e:
print("创建支付实例失败:", e) # 输出:创建支付实例失败:不支持的支付方式:applepay,支持的类型:wechat/alipay/unionpay
运行结果:
微信支付成功:100元(订单号:WX1407152043190881024100)
支付宝支付成功:200元(订单号:
ALIPAY1407152043190881088200)
银联支付成功:300元(订单号:
UNION1407152043190881152300)
创建支付实例失败:不支持的支付方式:applepay,支持的类型:wechat/alipay/unionpay
工厂模式的优势与适用场景
优势
- 解耦创建与使用:外部只需与工厂类交互,无需关心子类的具体创建逻辑(如无需知道WeChatPayment的构造参数);
- 统一管理创建逻辑:所有子类的创建都集中在工厂类中,后续修改子类名或新增子类,只需修改工厂类,无需修改所有调用代码(符合 “开闭原则”);
- 支持多态:工厂返回的对象都遵循统一接口(如Payment),外部调用时可直接使用统一方法(如pay),体现多态特性。
适用场景
- 子类较多且创建逻辑复杂:如支付系统、日志系统(支持文件日志、控制台日志、数据库日志);
- 需要统一管理对象创建:如框架中的插件加载、依赖注入;
- 希望隐藏子类实现细节:如第三方库对外提供工厂类,隐藏内部子类的具体实现。
7.4 本章实战:用 “工厂模式” 创建不同类型的 “支付方式”(微信支付、支付宝支付)
本实战将基于 7.3.2 节的支付场景,进一步完善工厂模式的实现,增加 “支付前校验” 和 “支付记录” 功能,具体需求如下:
实战需求
- 定义Payment抽象类(继承ABC):
- 抽象方法:pay(self, amount)(发起支付,返回支付结果字符串);
- 普通方法:validate_payment(self, amount)(支付前校验:金额必须为正数,返回布尔值和错误信息)。
- 定义具体支付子类(继承Payment):
- WeChatPayment:微信支付,pay方法返回 “微信支付成功,订单号:WX + 时间戳 + 随机数”;
- AlipayPayment:支付宝支付,pay方法返回 “支付宝支付成功,订单号:ALIPAY + 时间戳 + 随机数”。
- 定义PaymentFactory工厂类:
- 静态方法create_payment(payment_type, app_id):根据payment_type("wechat"/"alipay")创建对应支付实例,app_id为支付平台的应用 ID(需传入子类初始化);
- 校验payment_type合法性,不支持则抛出异常。
- 定义PaymentRecord类(记录支付记录):
- 静态方法log_payment(payment_result):接收支付结果字符串,打印 “[支付时间] 支付记录:{payment_result}”(支付时间格式:年 - 月 - 日 时:分: 秒)。
- 完成以下操作:
- 通过工厂类创建微信支付实例(app_id="wx123456")和支付宝支付实例(app_id="alipay789");
- 分别发起 150 元(微信)和 280 元(支付宝)的支付,先调用validate_payment校验,校验通过再调用pay;
- 调用PaymentRecord.log_payment记录每次支付结果;
- 尝试创建 “银联支付” 实例(不支持),观察异常处理。
实战代码实现
import time
import random
from abc import ABC, abstractmethod
# 1. 定义Payment抽象类(抽象产品)
class Payment(ABC):
def __init__(self, app_id):
# 初始化支付平台的应用ID(每个支付实例对应一个app_id)
self.app_id = app_id
def validate_payment(self, amount):
"""
支付前校验:金额必须为正数
:param amount: 支付金额
:return: (is_valid: bool, message: str) 校验结果和提示信息
"""
if not isinstance(amount, (int, float)):
return False, "支付金额必须是整数或浮点数"
if amount <= 0:
return False, f"支付金额{amount}元无效,必须大于0"
return True, "校验通过"
@abstractmethod
def pay(self, amount):
"""抽象方法:发起支付,返回支付结果字符串"""
pass
# 2. 定义具体支付子类(具体产品)
class WeChatPayment(Payment):
def pay(self, amount):
# 生成订单号:WX + 时间戳(秒) + 6位随机数
timestamp = int(time.time())
random_num = random.randint(100000, 999999)
order_id = f"WX{timestamp}{random_num}"
# 返回支付结果(包含app_id,区分不同应用)
return f"微信支付(app_id:{self.app_id})成功:{amount}元,订单号:{order_id}"
class AlipayPayment(Payment):
def pay(self, amount):
# 生成订单号:ALIPAY + 时间戳(秒) + 6位随机数
timestamp = int(time.time())
random_num = random.randint(100000, 999999)
order_id = f"ALIPAY{timestamp}{random_num}"
# 返回支付结果
return f"支付宝支付(app_id:{self.app_id})成功:{amount}元,订单号:{order_id}"
# 3. 定义PaymentFactory工厂类
class PaymentFactory:
@staticmethod
def create_payment(payment_type, app_id):
"""
根据支付类型创建支付实例
:param payment_type: 支付类型("wechat"/"alipay")
:param app_id: 支付平台的应用ID
:return: 对应的支付实例(
WeChatPayment/AlipayPayment)
"""
# 校验app_id合法性
if not isinstance(app_id, str) or len(app_id.strip()) == 0:
raise ValueError("app_id必须是非空字符串")
# 根据支付类型创建实例
payment_type = payment_type.lower()
if payment_type == "wechat":
return WeChatPayment(app_id.strip())
elif payment_type == "alipay":
return AlipayPayment(app_id.strip())
else:
raise ValueError(f"不支持的支付方式:{payment_type},仅支持wechat/alipay")
# 4. 定义PaymentRecord类(支付记录)
class PaymentRecord:
@staticmethod
def log_payment(payment_result):
"""
记录支付结果,包含当前时间
:param payment_result: 支付结果字符串(由pay方法返回)
"""
# 获取当前时间,格式:年-月-日 时:分:秒
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"[{current_time}] 支付记录:{payment_result}")
# 5. 实战操作:执行支付流程
def execute_payment(payment, amount):
"""
执行支付流程:校验 → 支付 → 记录
:param payment: 支付实例(
WeChatPayment/AlipayPayment)
:param amount: 支付金额
"""
# 步骤1:支付前校验
is_valid, message = payment.validate_payment(amount)
if not is_valid:
print(f"支付失败:{message}")
return
# 步骤2:发起支付
try:
payment_result = payment.pay(amount)
print(f"支付成功:{payment_result.split(',')[0]}") # 简化打印支付成功信息
except Exception as e:
print(f"支付异常:{e}")
return
# 步骤3:记录支付结果
PaymentRecord.log_payment(payment_result)
# 执行具体支付
if __name__ == "__main__":
print("=" * 60)
# 场景1:微信支付150元
try:
wechat_pay = PaymentFactory.create_payment("wechat", "wx123456")
execute_payment(wechat_pay, 150)
except ValueError as e:
print(f"微信支付初始化失败:{e}")
print("\n" + "=" * 60)
# 场景2:支付宝支付280元
try:
alipay_pay = PaymentFactory.create_payment("alipay", "alipay789")
execute_payment(alipay_pay, 280)
except ValueError as e:
print(f"支付宝支付初始化失败:{e}")
print("\n" + "=" * 60)
# 场景3:尝试支付负数金额(校验失败)
try:
wechat_pay2 = PaymentFactory.create_payment("wechat", "wx789012")
execute_payment(wechat_pay2, -50) # 金额为负,校验失败
except ValueError as e:
print(f"微信支付初始化失败:{e}")
print("\n" + "=" * 60)
# 场景4:尝试创建银联支付(不支持)
try:
unionpay_pay = PaymentFactory.create_payment("unionpay", "union123")
except ValueError as e:
print(f"银联支付初始化失败:{e}")
实战运行结果
============================================================
支付成功:微信支付(app_id:wx123456)成功:150元
[2025-08-25 16:30:45] 支付记录:微信支付(app_id:wx123456)成功:150元,订单号:WX1756203445123456
============================================================
支付成功:支付宝支付(app_id:alipay789)成功:280元
[2025-08-25 16:30:45] 支付记录:支付宝支付(app_id:alipay789)成功:280元,订单号:ALIPAY1756203445654321
============================================================
支付失败:支付金额-50元无效,必须大于0
============================================================
银联支付初始化失败:不支持的支付方式:unionpay,仅支持wechat/alipay
实战代码解析
- 抽象类Payment的设计:
- 构造方法__init__接收app_id,确保每个支付实例绑定唯一的应用 ID(模拟真实支付平台的应用标识);
- 普通方法validate_payment封装通用校验逻辑,子类无需重复编写,体现代码复用;
- 抽象方法pay强制子类实现差异化的支付逻辑,确保接口统一。
- 具体支付子类的设计:
- WeChatPayment和AlipayPayment分别实现pay方法,生成包含 “支付类型、app_id、金额、订单号” 的结果字符串;
- 订单号通过 “时间戳 + 随机数” 生成,确保唯一性(模拟真实支付系统的订单生成规则)。
- 工厂类PaymentFactory的设计:
- 静态方法create_payment新增app_id参数,支持传入支付平台的应用标识,满足实际开发中 “多应用” 的场景;
- 增加app_id合法性校验,避免创建无效的支付实例;
- 统一管理支付类型的判断逻辑,后续新增支付方式(如 “银联支付”)时,只需在工厂类中添加分支,无需修改其他代码。
- 支付记录类PaymentRecord的设计:
- 静态方法log_payment通过time.strftime获取格式化时间,记录支付的具体时间点,模拟真实系统的日志记录功能;
- 与支付逻辑解耦,支付记录功能可独立扩展(如后续需将记录写入文件或数据库,只需修改该类)。
- 统一支付流程execute_payment的设计:
- 封装 “校验→支付→记录” 的完整流程,外部调用只需传入 “支付实例” 和 “金额”,无需关心内部步骤;
- 增加异常捕获,确保某一步骤失败时不影响整体程序运行,提升代码健壮性。
实战总结
通过本次工厂模式实战,我们实现了以下目标:
- 接口统一:所有支付方式都遵循Payment抽象类的接口,外部调用时无需区分具体类型,体现多态特性;
- 创建解耦:通过工厂类统一管理支付实例的创建,外部只需指定 “支付类型” 和 “app_id”,无需直接调用子类构造函数;
- 逻辑复用:通用的校验逻辑、日志记录逻辑被封装在单独的方法或类中,避免代码冗余;
- 易于扩展:新增支付方式(如 “银联支付”)时,只需:
- 创建UnionPayPayment类继承Payment并实现pay方法;
- 在PaymentFactory.create_payment中添加 “unionpay” 的分支判断;
无需修改execute_payment、PaymentRecord等现有代码,完全符合 “开闭原则”。
本章小结
- 类方法与静态方法:
- 类方法依赖cls参数,用于操作类属性或创建实例,适合实现 “类级别的逻辑”;
- 静态方法无默认参数,用于提供纯工具函数,适合封装与类相关但不依赖类 / 实例属性的逻辑;
- 二者均通过装饰器定义,推荐通过 “类名。方法名” 调用,避免与实例方法混淆。
- 设计模式的实践价值:
- 单例模式确保类只有一个实例,适合资源密集型或全局共享的对象(如数据库连接、配置管理);
- 工厂模式统一管理子类对象的创建,降低代码耦合度,提升扩展性,适合 “多子类、需统一创建” 的场景(如支付系统、日志系统);
- 设计模式不是 “银弹”,需根据实际场景选择 —— 简单场景下无需过度设计,复杂场景下合理应用可显著提升代码质量。
- Python 特性与设计模式的结合:
- 利用abc模块实现抽象类,强制接口统一;
- 利用装饰器、模块特性等 Python 特有语法,简化设计模式的实现(如基于装饰器的单例、基于模块的单例);
- 设计模式的核心是 “解决问题的思想”,而非固定代码模板,需结合 Python 的动态特性灵活调整。
本章的进阶内容为后续复杂项目开发奠定了基础,下一章我们将通过 “学生信息管理系统” 的综合实战,整合 OOP 的核心特性与进阶技巧,实现一个完整的小型项目。