首页 健康生活文章正文

Python面向对象编程第 7 章:类方法、静态方法与设计模式入门

健康生活 2025年08月26日 04:04 1 admin

在掌握了 OOP 三大核心特性(封装、继承、多态)后,我们需要进一步学习 Python 中类的进阶方法(类方法、静态方法)和实用设计模式。类方法和静态方法拓展了类的功能边界,分别用于 “操作类属性” 和 “提供工具函数”;而设计模式则是 OOP 思想的经典实践,能帮助我们解决复杂场景下的代码设计问题。本章将从类方法、静态方法的语法与应用,到单例模式、工厂模式的实现,逐步提升你的 OOP 实战能力。

7.1 类方法(@classmethod)

Python面向对象编程第 7 章:类方法、静态方法与设计模式入门

在之前的学习中,我们接触的主要是 “实例方法”—— 依赖于对象,第一个参数必须是self,用于操作实例属性。而类方法是依赖于 “类” 的方法,通过@classmethod装饰器定义,第一个参数是cls(代表当前类,是 Python 的约定,不可省略),主要用于操作类属性或创建类的实例。

7.1.1 类方法的定义与调用

定义语法

class 类名:

# 类属性(类方法通常操作类属性)

类属性名 = 初始值

# 类方法:用@classmethod装饰,第一个参数是cls

@classmethod

def 类方法名(cls, 参数1, 参数2, ...):

# 方法逻辑:可通过cls访问类属性或调用其他类方法

cls.类属性名 = 新值 # 修改类属性

return 结果

语法说明

  1. @classmethod装饰器:必须放在类方法定义的上方,用于标识该方法是类方法;
  1. cls参数:代表当前类(如Student类的类方法中,cls就是Student类本身),通过cls可访问类属性、调用其他类方法,或创建类的实例;
  1. 调用方式:既可以通过 “类名.类方法名(参数)” 调用(推荐),也可以通过 “对象.类方法名(参数)” 调用(不推荐,易混淆实例方法)。

示例:类方法操作类属性

假设我们需要统计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

语法说明

  1. @staticmethod装饰器:必须放在静态方法定义的上方,用于标识该方法是静态方法;
  1. 无默认参数:静态方法不依赖self(实例)或cls(类),因此没有默认参数,参数完全由调用者传入;
  1. 调用方式:既可以通过 “类名.静态方法名(参数)” 调用(推荐),也可以通过 “对象.静态方法名(参数)” 调用(不推荐)。

示例:静态方法实现工具函数

假设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}$'

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 方法选择的实用建议

在实际开发中,选择哪种方法需根据 “是否依赖类 / 实例属性” 来判断,具体建议如下:

  1. 选择实例方法
    • 方法需要访问或修改实例属性(如Student的get_score、set_score);
    • 方法是对象的 “行为”(如Dog的bark、Student的study)。
  1. 选择类方法
    • 方法需要访问或修改类属性(如统计实例数量、更新类配置);
    • 方法需要创建类的实例(如从 JSON、CSV 加载数据创建实例);
    • 方法的逻辑与 “类” 相关,与 “具体实例” 无关(如Math类的sum方法,计算多个数字的和)。
  1. 选择静态方法
    • 方法是纯工具函数,仅依赖传入的参数,不访问类属性和实例属性(如格式验证、数据转换);
    • 方法与类相关,但不需要与类或实例绑定(如Date类的is_leap_year方法,判断某一年是否为闰年)。

7.3 OOP 设计模式入门(实用篇)

设计模式是 OOP 开发中总结的 “可复用解决方案”,用于解决特定场景下的代码设计问题(如 “确保一个类只有一个实例”“统一创建不同子类的对象”)

。掌握设计模式能让你的代码更具可读性、可维护性和扩展性,避免重复 “造轮子”。本节将介绍两种最常用的设计模式:单例模式(确保类只有一个实例)和工厂模式(统一创建子类对象),并结合 Python 特性讲解实现方式。

7.3.1 单例模式:保证一个类只有一个实例

在某些场景下,我们需要确保一个类只能创建一个实例(如数据库连接池、配置管理器、日志对象)—— 若创建多个实例,可能导致资源冲突(如多个数据库连接修改同一数据)或资源浪费(如重复创建重量级对象)。单例模式(Singleton Pattern)就是解决这类问题的经典方案。

单例模式的核心思想

  1. 私有构造:通过私有化__init__方法或__new__方法(Python 中创建对象的底层方法),阻止外部直接实例化;
  1. 全局访问点:提供一个静态方法或类方法,作为获取唯一实例的全局接口,确保每次调用都返回同一个实例。

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 只会创建一个模块对象,不会重复加载。利用这一特性,可将单例实例定义在模块中,实现单例模式。

步骤

  1. 创建singleton_module.py模块,在模块中创建类的实例;
  1. 其他文件通过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 模块特性即可实现;
  • 无副作用:不会修改类的原有逻辑,兼容性好。

单例模式的适用场景

  1. 资源密集型对象:如数据库连接、网络连接、线程池等,创建成本高,重复创建会浪费资源;
  1. 全局配置对象:如配置管理器,整个系统只需一个配置实例,确保所有模块使用统一配置;
  1. 日志对象:日志写入需保证顺序,多个日志实例可能导致日志内容混乱;
  1. 工具类对象:如日期工具、加密工具等,无需多个实例,全局共享一个即可。

7.3.2 工厂模式:通过 “工厂类” 统一创建不同子类对象

在多继承或子类较多的场景下,直接通过子类名()创建对象会导致 “创建逻辑分散”—— 若后续子类名修改或新增子类,需修改所有创建对象的代码。工厂模式(Factory Pattern)通过定义一个 “工厂类”,统一负责所有子类对象的创建,外部只需告诉工厂 “要创建什么类型的对象”,无需关心具体创建逻辑。

工厂模式的核心思想

  1. 抽象产品:定义所有产品(子类)的统一接口(如Payment抽象类,包含pay方法);
  1. 具体产品:实现抽象产品接口的子类(如WeChatPayment、AlipayPayment);
  1. 工厂类:提供静态方法或类方法,根据传入的参数,创建并返回对应的具体产品实例(如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

工厂模式的优势与适用场景

优势

  1. 解耦创建与使用:外部只需与工厂类交互,无需关心子类的具体创建逻辑(如无需知道WeChatPayment的构造参数);
  1. 统一管理创建逻辑:所有子类的创建都集中在工厂类中,后续修改子类名或新增子类,只需修改工厂类,无需修改所有调用代码(符合 “开闭原则”);
  1. 支持多态:工厂返回的对象都遵循统一接口(如Payment),外部调用时可直接使用统一方法(如pay),体现多态特性。

适用场景

  1. 子类较多且创建逻辑复杂:如支付系统、日志系统(支持文件日志、控制台日志、数据库日志);
  1. 需要统一管理对象创建:如框架中的插件加载、依赖注入;
  1. 希望隐藏子类实现细节:如第三方库对外提供工厂类,隐藏内部子类的具体实现。

7.4 本章实战:用 “工厂模式” 创建不同类型的 “支付方式”(微信支付、支付宝支付)

本实战将基于 7.3.2 节的支付场景,进一步完善工厂模式的实现,增加 “支付前校验” 和 “支付记录” 功能,具体需求如下:

实战需求

  1. 定义Payment抽象类(继承ABC):
    • 抽象方法:pay(self, amount)(发起支付,返回支付结果字符串);
    • 普通方法:validate_payment(self, amount)(支付前校验:金额必须为正数,返回布尔值和错误信息)。
  1. 定义具体支付子类(继承Payment):
    • WeChatPayment:微信支付,pay方法返回 “微信支付成功,订单号:WX + 时间戳 + 随机数”;
    • AlipayPayment:支付宝支付,pay方法返回 “支付宝支付成功,订单号:ALIPAY + 时间戳 + 随机数”。
  1. 定义PaymentFactory工厂类:
    • 静态方法create_payment(payment_type, app_id):根据payment_type("wechat"/"alipay")创建对应支付实例,app_id为支付平台的应用 ID(需传入子类初始化);
    • 校验payment_type合法性,不支持则抛出异常。
  1. 定义PaymentRecord类(记录支付记录):
    • 静态方法log_payment(payment_result):接收支付结果字符串,打印 “[支付时间] 支付记录:{payment_result}”(支付时间格式:年 - 月 - 日 时:分: 秒)。
  1. 完成以下操作:
    • 通过工厂类创建微信支付实例(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

实战代码解析

  1. 抽象类Payment的设计
    • 构造方法__init__接收app_id,确保每个支付实例绑定唯一的应用 ID(模拟真实支付平台的应用标识);
    • 普通方法validate_payment封装通用校验逻辑,子类无需重复编写,体现代码复用;
    • 抽象方法pay强制子类实现差异化的支付逻辑,确保接口统一。
  1. 具体支付子类的设计
    • WeChatPayment和AlipayPayment分别实现pay方法,生成包含 “支付类型、app_id、金额、订单号” 的结果字符串;
    • 订单号通过 “时间戳 + 随机数” 生成,确保唯一性(模拟真实支付系统的订单生成规则)。
  1. 工厂类PaymentFactory的设计
    • 静态方法create_payment新增app_id参数,支持传入支付平台的应用标识,满足实际开发中 “多应用” 的场景;
    • 增加app_id合法性校验,避免创建无效的支付实例;
    • 统一管理支付类型的判断逻辑,后续新增支付方式(如 “银联支付”)时,只需在工厂类中添加分支,无需修改其他代码。
  1. 支付记录类PaymentRecord的设计
    • 静态方法log_payment通过time.strftime获取格式化时间,记录支付的具体时间点,模拟真实系统的日志记录功能;
    • 与支付逻辑解耦,支付记录功能可独立扩展(如后续需将记录写入文件或数据库,只需修改该类)。
  1. 统一支付流程execute_payment的设计
    • 封装 “校验→支付→记录” 的完整流程,外部调用只需传入 “支付实例” 和 “金额”,无需关心内部步骤;
    • 增加异常捕获,确保某一步骤失败时不影响整体程序运行,提升代码健壮性。

实战总结

通过本次工厂模式实战,我们实现了以下目标:

  1. 接口统一:所有支付方式都遵循Payment抽象类的接口,外部调用时无需区分具体类型,体现多态特性;
  1. 创建解耦:通过工厂类统一管理支付实例的创建,外部只需指定 “支付类型” 和 “app_id”,无需直接调用子类构造函数;
  1. 逻辑复用:通用的校验逻辑、日志记录逻辑被封装在单独的方法或类中,避免代码冗余;
  1. 易于扩展:新增支付方式(如 “银联支付”)时,只需:
    • 创建UnionPayPayment类继承Payment并实现pay方法;
    • 在PaymentFactory.create_payment中添加 “unionpay” 的分支判断;

无需修改execute_payment、PaymentRecord等现有代码,完全符合 “开闭原则”。

本章小结

  1. 类方法与静态方法
    • 类方法依赖cls参数,用于操作类属性或创建实例,适合实现 “类级别的逻辑”;
    • 静态方法无默认参数,用于提供纯工具函数,适合封装与类相关但不依赖类 / 实例属性的逻辑;
    • 二者均通过装饰器定义,推荐通过 “类名。方法名” 调用,避免与实例方法混淆。
  1. 设计模式的实践价值
    • 单例模式确保类只有一个实例,适合资源密集型或全局共享的对象(如数据库连接、配置管理);
    • 工厂模式统一管理子类对象的创建,降低代码耦合度,提升扩展性,适合 “多子类、需统一创建” 的场景(如支付系统、日志系统);
    • 设计模式不是 “银弹”,需根据实际场景选择 —— 简单场景下无需过度设计,复杂场景下合理应用可显著提升代码质量。
  1. Python 特性与设计模式的结合
    • 利用abc模块实现抽象类,强制接口统一;
    • 利用装饰器、模块特性等 Python 特有语法,简化设计模式的实现(如基于装饰器的单例、基于模块的单例);
    • 设计模式的核心是 “解决问题的思想”,而非固定代码模板,需结合 Python 的动态特性灵活调整。

本章的进阶内容为后续复杂项目开发奠定了基础,下一章我们将通过 “学生信息管理系统” 的综合实战,整合 OOP 的核心特性与进阶技巧,实现一个完整的小型项目。

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动