news 2026/9/10 10:41:12

agents24 之 python-design-patterns 技能详解:从 KISS 到依赖注入的可维护 Python 架构指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
agents24 之 python-design-patterns 技能详解:从 KISS 到依赖注入的可维护 Python 架构指南

agents24 之 python-design-patterns 技能详解:从 KISS 到依赖注入的可维护 Python 架构指南

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

本篇技术指南围绕 agents24 仓库中plugins/python-development/skills/python-design-patterns技能展开,系统讲解一套面向生产环境的 Python 设计原则:KISS、单一职责、关注点分离、组合优于继承、三振出局(Rule of Three)、函数粒度控制、依赖注入以及常见反模式规避。文章完整继承该技能references/details.md的 8 个核心模式与全部可运行示例,并结合SKILL.md的最佳实践清单、排查指南及仓库内相邻技能进行纵深扩充,帮助你写出易于理解、测试与修改的 Python 代码。

技能定位与适用场景

python-design-patterns是 agents24 仓库中 python-development 插件体系下的核心技能之一。它在设计上采用"导航层 + 详情层"两级结构:上层 SKILL.md 提供核心概念速览、快速上手代码与最佳实践摘要;下层 references/details.md 提供完整的模式讲解与带注释的可运行示例。当导航层信息不足以支撑决策时,Agent 或开发者应下沉阅读详情层。

该技能的触发场景(来自 SKILL.md)覆盖了从设计到评审的全生命周期:

  • 从零设计新服务或组件,决定如何分层职责;
  • 重构不断膨胀的上帝类(God Class)或巨型函数;
  • 决定是新增抽象还是容忍重复;
  • 评审 Pull Request 中的结构性缺陷(紧耦合、内部类型泄露);
  • 在新类层次结构中抉择继承与组合;
  • 代码因 I/O 与业务逻辑纠缠而难以测试时。

一句话概括技能的核心主张:模式不是装饰,而是解决真实问题的工具。它关注的是可维护性、可测试性与可读性,而非堆砌设计模式的数量。

四大基础模式

模式 1:KISS —— 保持简单

在引入任何复杂度之前,先问一句:更简单的方案能否解决问题?

详情层文档给出的对照示例极具说服力。先看"过度设计"的写法——用带注册机制的工厂模式实现格式化器分发:

# Over-engineered: Factory with registration class OutputFormatterFactory: _formatters: dict[str, type[Formatter]] = {} @classmethod def register(cls, name: str): def decorator(formatter_cls): cls._formatters[name] = formatter_cls return formatter_cls return decorator @classmethod def create(cls, name: str) -> Formatter: return cls._formatters[name]() @OutputFormatterFactory.register("json") class JsonFormatter(Formatter): ...

再看"简单"的写法——直接用一个字典:

# Simple: Just use a dictionary FORMATTERS = { "json": JsonFormatter, "csv": CsvFormatter, "xml": XmlFormatter, } def get_formatter(name: str) -> Formatter: """Get formatter by name.""" if name not in FORMATTERS: raise ValueError(f"Unknown format: {name}") return FORMATTERS[name]()

详情层给出的结论值得反复咀嚼:"The factory pattern adds code without adding value here. Save patterns for when they solve real problems."(工厂模式在此处只增加代码而不增加价值,把模式留到能解决真实问题的时候再用。)这也呼应了 SKILL.md 的 Quick Start:简单胜过花哨(Simple beats clever)。注意简单版本额外补了一个ValueError兜底——健壮性与简洁并不冲突。

模式 2:单一职责原则(SRP)

每个类或函数应当只有一个"变更理由"(reason to change)。详情层用UserHandler给出了正反示例。

反例——Handler 什么都做:HTTP 解析、字段校验、数据库写入、响应格式化全部揉在一个async def create_user里:

# BAD: Handler does everything class UserHandler: async def create_user(self, request: Request) -> Response: # HTTP parsing data = await request.json() # Validation if not data.get("email"): return Response({"error": "email required"}, status=400) # Database access user = await db.execute( "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *", data["email"], data["name"] ) # Response formatting return Response({"id": user.id, "email": user.email}, status=201)

正例——拆分为两个职责清晰的类:

# GOOD: Separated concerns class UserService: """Business logic only.""" def __init__(self, repo: UserRepository) -> None: self._repo = repo async def create_user(self, data: CreateUserInput) -> User: # Only business rules here user = User(email=data.email, name=data.name) return await self._repo.save(user) class UserHandler: """HTTP concerns only.""" def __init__(self, service: UserService) -> None: self._service = service async def create_user(self, request: Request) -> Response: data = CreateUserInput(**(await request.json())) user = await self._service.create_user(data) return Response(user.to_dict(), status=201)

拆分后的收益如详情层所言:HTTP 层的变化不会影响业务逻辑,反之亦然("Now HTTP changes don't affect business logic, and vice versa.")。值得注意的细节是正例中的类型设计:请求先经CreateUserInput数据类/Pydantic 模型转换,再传入 Service——这为后续的"在 API 边界尽早校验"打好了基础,与 python-anti-patterns 中"缺失输入校验"反模式的修复方案(CreateUserInput.model_validate(data))形成呼应。

模式 3:关注点分离(SoC)

将代码组织为职责明确的独立层次,每一层只依赖其下层。详情层给出了经典的 API → Service → Repository 三层架构图:

┌─────────────────────────────────────────────────────┐ │ API Layer (handlers) │ │ - Parse requests │ │ - Call services │ │ - Format responses │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Service Layer (business logic) │ │ - Domain rules and validation │ │ - Orchestrate operations │ │ - Pure functions where possible │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Repository Layer (data access) │ │ - SQL queries │ │ - External API calls │ │ - Cache operations │ └─────────────────────────────────────────────────────┘
  • API 层(handlers):解析请求、调用服务、格式化响应;
  • 服务层(业务逻辑):领域规则与校验、操作编排、尽可能写成纯函数;
  • 仓储层(数据访问):SQL 查询、外部 API 调用、缓存操作。

配套的代码示例展示了每一层各司其职的完整形态:

# Repository: Data access class UserRepository: async def get_by_id(self, user_id: str) -> User | None: row = await self._db.fetchrow( "SELECT * FROM users WHERE id = $1", user_id ) return User(**row) if row else None # Service: Business logic class UserService: def __init__(self, repo: UserRepository) -> None: self._repo = repo async def get_user(self, user_id: str) -> User: user = await self._repo.get_by_id(user_id) if user is None: raise UserNotFoundError(user_id) return user # Handler: HTTP concerns @app.get("/users/{user_id}") async def get_user(user_id: str) -> UserResponse: user = await user_service.get_user(user_id) return UserResponse.from_user(user)

注意依赖箭头是单向向下的:Service 可以 import Repository,但 Repository 绝不能 import Handler。当出现"服务层反向导入 API 层"时,就是明确的层次违规(SKILL.md 的排查指南给出了修复方法:引入一个共享的 types/models 层,让两层共同依赖它,从而保持依赖方向 API → Service → Repository)。

这一分层思想与仓库内 python-project-structure 技能中的 Pattern 6 "Layered Architecture"(api/services/repositories/models/schemas/config/)完全同构,两个技能可以配合使用:前者解决"模块如何摆放",后者解决"类如何划分职责"。

模式 4:组合优于继承

通过组合对象来构建行为,而不是通过继承扩展类。详情层以通知服务为例对比了两种方式。

继承方式——僵硬且难以测试:EmailNotificationService__init__里直接SmtpClient(),导致依赖难以 mock:

# Inheritance: Rigid and hard to test class EmailNotificationService(NotificationService): def __init__(self): super().__init__() self._smtp = SmtpClient() # Hard to mock def notify(self, user: User, message: str) -> None: self._smtp.send(user.email, message)

组合方式——灵活且可测试:所有依赖通过构造函数注入,且各渠道是可选的:

# Composition: Flexible and testable class NotificationService: """Send notifications via multiple channels.""" def __init__( self, email_sender: EmailSender, sms_sender: SmsSender | None = None, push_sender: PushSender | None = None, ) -> None: self._email = email_sender self._sms = sms_sender self._push = push_sender async def notify( self, user: User, message: str, channels: set[str] | None = None, ) -> None: channels = channels or {"email"} if "email" in channels: await self._email.send(user.email, message) if "sms" in channels and self._sms and user.phone: await self._sms.send(user.phone, message) if "push" in channels and self._push and user.device_token: await self._push.send(user.device_token, message) # Easy to test with fakes service = NotificationService( email_sender=FakeEmailSender(), sms_sender=FakeSmsSender(), )

关键细节:

  • sms_senderpush_sender参数默认值为None,配合if self._sms and user.phone的短路判断,实现可选渠道的动态能力组合,这在继承体系中通常要靠多层子类才能实现;
  • 测试时直接注入FakeEmailSenderFakeSmsSender等替身(fakes),无需任何 mock 框架——这正是组合带来的可测试性红利。

进阶模式

模式 5:三振出局(Rule of Three)

等到出现第三个实例时,再考虑抽象。

详情层用订单处理与退货处理两个"看起来相似"的函数说明:重复常常好过错误的抽象

# Two similar functions? Don't abstract yet def process_orders(orders: list[Order]) -> list[Result]: results = [] for order in orders: validated = validate_order(order) result = process_validated_order(validated) results.append(result) return results def process_returns(returns: list[Return]) -> list[Result]: results = [] for ret in returns: validated = validate_return(ret) result = process_validated_return(validated) results.append(result) return results # These look similar, but wait! Are they actually the same? # Different validation, different processing, different errors... # Duplication is often better than the wrong abstraction # Only after a third case, consider if there's a real pattern # But even then, sometimes explicit is better than abstract

两个函数表面结构相同(遍历 → 校验 → 处理 → 收集),但校验逻辑、处理逻辑、错误语义各不相同。过早抽象会强行统一这些差异,最终生成一个布满 if/else 分支的"万能函数"。只有出现第三个相似案例时,才值得认真评估是否存在真正的共同模式——而且即便如此,"显式优于抽象"(explicit is better than abstract)依然是值得保留的立场。

SKILL.md 的排查指南补充了这条规则的边界条件:Rule of Three 是启发式而非法律。如果重复的副本已经出现"危险的分叉"(一处更新而另一处未更新,导致 bug),就应该立即抽象,并补一个覆盖共享行为的测试。

模式 6:函数粒度控制

保持函数聚焦。当函数出现以下信号时,应考虑拆分:

  • 超过 20-50 行(视复杂度而定);
  • 承担多个不同目的;
  • 存在深层嵌套逻辑(3 层及以上)。

详情层用订单处理函数演示了"巨型函数 → 组合式函数"的转变:

# Too long, multiple concerns mixed def process_order(order: Order) -> Result: # 50 lines of validation... # 30 lines of inventory check... # 40 lines of payment processing... # 20 lines of notification... pass # Better: Composed from focused functions def process_order(order: Order) -> Result: """Process a customer order through the complete workflow.""" validate_order(order) reserve_inventory(order) payment_result = charge_payment(order) send_confirmation(order, payment_result) return Result(success=True, order_id=order.id)

注意重构后的process_order保留了 docstring("Process a customer order through the complete workflow."),这是关键:组合式函数仍然有一个清晰的整体叙事,只是把具体实现下放到各聚焦函数中。这一粒度标准与 python-project-structure 中"单文件 300-500 行可考虑拆分"的文件级标准互为补充——函数粒度管函数,文件粒度管模块。

模式 7:依赖注入(DI)

通过构造函数传递依赖,以获得可测试性。详情层展示了一个同时注入repositorycachelogger三个依赖的完整示例,并特别使用了typing.Protocol定义抽象依赖——这是现代 Python 中"结构化子类型"(structural subtyping)的典型应用:

from typing import Protocol class Logger(Protocol): def info(self, msg: str, **kwargs) -> None: ... def error(self, msg: str, **kwargs) -> None: ... class Cache(Protocol): async def get(self, key: str) -> str | None: ... async def set(self, key: str, value: str, ttl: int) -> None: ... class UserService: """Service with injected dependencies.""" def __init__( self, repository: UserRepository, cache: Cache, logger: Logger, ) -> None: self._repo = repository self._cache = cache self._logger = logger async def get_user(self, user_id: str) -> User: # Check cache first cached = await self._cache.get(f"user:{user_id}") if cached: self._logger.info("Cache hit", user_id=user_id) return User.from_json(cached) # Fetch from database user = await self._repo.get_by_id(user_id) if user: await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300) return user # Production service = UserService( repository=PostgresUserRepository(db), cache=RedisCache(redis), logger=StructlogLogger(), ) # Testing service = UserService( repository=InMemoryUserRepository(), cache=FakeCache(), logger=NullLogger(), )

这个示例信息量很大,值得逐点拆解:

  1. Protocol 而非抽象基类LoggerCache仅声明方法签名(...占位),不要求继承关系。任何具有get(key) -> str | None等签名匹配的对象,无论是否显式继承,都满足Cache协议——测试中可以直接传FakeCache()
  2. 同一套生产代码,两套组装配置:生产环境注入PostgresUserRepositoryRedisCacheStructlogLogger;测试环境注入InMemoryUserRepositoryFakeCacheNullLogger。服务本身零改动——这就是依赖注入的核心价值;
  3. 缓存 + 日志的业务闭环get_user展示了缓存命中短路(Cache hit日志 + 直接从 JSON 反序列化)、缓存未命中回源数据库、成功后再回填缓存(TTL 300 秒)的完整流程;
  4. 跨技能印证:这种"构造器注入 + 每层隔离测试"的结构,正是 python-testing-patterns 技能中单元测试与 mock 策略得以实施的前提。

值得注意的反模式信号(SKILL.md):当构造器参数膨胀到 7 个以上时,这不是 DI 的问题,而是该类职责过多的信号——应该先拆分类的职责,构造函数自然会变小。

模式 8:规避常见反模式

详情层在本节给出两组"坏 / 好"对照,直指评审中最常见的两类结构性问题。

不要暴露内部类型:

# BAD: Leaking ORM model to API @app.get("/users/{id}") def get_user(id: str) -> UserModel: # SQLAlchemy model return db.query(UserModel).get(id) # GOOD: Use response schemas @app.get("/users/{id}") def get_user(id: str) -> UserResponse: user = db.query(UserModel).get(id) return UserResponse.from_orm(user)

将 SQLAlchemy 模型直接作为 API 返回值,会把数据库层的字段、延迟加载行为、内部命名全部泄露给客户端。正确做法是通过响应模式(response schema)显式定义对外契约。这与 python-anti-patterns 中的 "Exposed Internal Types" 反模式完全一致(其修复建议同样是 DTO/response models,如UserResponse.from_orm(user))。

不要把 I/O 与业务逻辑混在一起:

# BAD: SQL embedded in business logic def calculate_discount(user_id: str) -> float: user = db.query("SELECT * FROM users WHERE id = ?", user_id) orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id) # Business logic mixed with data access # GOOD: Repository pattern def calculate_discount(user: User, order_history: list[Order]) -> float: # Pure business logic, easily testable if len(order_history) > 10: return 0.15 return 0.0

重构后的calculate_discount接收userorder_history两个已就绪的参数,返回纯计算结果(超过 10 笔订单打 8.5 折,否则无折扣)——不碰数据库、不做 I/O,因此可以脱离数据库环境进行单元测试。这正是详情层三层架构中"Service Layer 尽可能写纯函数"原则的具体落地。

最佳实践清单与排查指南

SKILL.md 在详情层之上给出了十条可执行的最佳实践摘要,可作为日常开发的 checklist:

  1. 保持简单——选择能工作的最简单方案;
  2. 单一职责——每个单元只有一个变更理由;
  3. 关注点分离——层次清晰、目的明确;
  4. 组合而非继承——组合对象换取灵活性;
  5. 三振出局——抽象前先等待;
  6. 保持函数短小——20-50 行(视复杂度而定),一个目的;
  7. 注入依赖——构造器注入换取可测试性;
  8. 先删后抽象——先删除死代码,再考虑模式;
  9. 逐层测试——每个关注点独立测试;
  10. 显式优于聪明——可读代码胜过优雅代码。

其中第 8 条"Delete before abstracting"是常被忽略的一步:重构前先清理死代码,往往能让"是否需要抽象"的答案自动浮现。

针对实际落地中最常见的困惑,SKILL.md 提供了五组 Q&A 排查指南:

Q1:类在膨胀、似乎有多重职责,但拆分感觉不对。应用"变更理由测试":列出所有可能导致该类被修改的原因。如果列表中的条目来自不同领域(如 HTTP 解析 AND 业务规则 AND 格式化),就拆分;如果全部来自同一领域关注点,类的大小可能是合适的。

Q2:构造器注入导致构造器参数超过 7 个。这是类职责过多的信号,而非 DI 的问题。先拆分大类,各子类构造器自然变小。

Q3:组合产生了深层的包装对象,难以追踪。保持组合浅层(2-3 层)。如果包装是唯一手段,考虑基于 Protocol 的方案或简单函数组合,而不是装饰器对象链。

Q4:Rule of Three 说不该抽象,但重复正因"一处更新另一处没更"而产生 bug。危险分叉的重复应当提前抽象。Rule of Three 是启发式而非法律;若副本已经错误分叉,立即抽取并补一个覆盖共享行为的测试。

Q5:服务层从 API 层导入,打破了依赖方向。这是层次违规。服务层不得从 handlers 导入;应引入共享的 types/models 层供两层共同依赖,保持 API → Service → Repository 的向下依赖箭头。

技能在仓库中的协同定位

python-design-patterns并非孤岛,它与仓库内多个相邻技能形成完整的 Python 工程实践闭环:

  • python-anti-patterns:与本文档构成"正反对照"。其文档开头即注明:"本技能聚焦于该避免什么,关于正面模式与架构请见 python-design-patterns 技能"。两个技能可配合用于评审:前者当 checklist 排查问题,后者提供修复所需的正面模式;
  • python-project-structure:解决模块组织与目录布局,让本技能的层次边界(API/Service/Repository)在工程结构上显式化;
  • python-testing-patterns:其文档同样将本技能列为前置——"利用这里建立的依赖注入结构逐层隔离测试"。DI 是测试隔离的前提,测试是验证 DI 结构的试金石;
  • python-pro:该 Agent 负责将上述技能落地为生产级代码,强调 PEP 8、类型注解、全面错误处理、>90% 测试覆盖率与标准库优先,与设计模式技能的理念一致。

此外,python-type-safety、python-error-handling 等技能也与本文档的 Protocol 类型、异常设计等内容存在天然关联。

结语:模式的正确打开方式

回到详情层开篇的立场:模式的价值在于解决真实问题。KISS 提醒你别为了模式而模式——字典能解决的问题不需要工厂;SRP 与 SoC 保证每一层、每个类都有清晰且单一的变更理由;组合优于继承让系统在测试与扩展时保持弹性;Rule of Three 与函数粒度控制防止过早抽象与巨型函数;依赖注入把可测试性建立在结构之上;而反模式规避则守住架构的底线。

这套技能的最终评判标准是 SKILL.md 中反复强调的三维目标:易于理解(easy to understand)、易于测试(easy to test)、易于修改(easy to modify)。当你下次设计新服务、重构上帝类或评审 PR 时,不妨逐条对照本文的模式与排查指南,让设计决策有据可依。

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 10:38:47

CVAT 国际化完全指南:3 个 i18n 入口与语言包配置一次讲清

CVAT 国际化完全指南:3 个 i18n 入口与语言包配置一次讲清 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise produc…

作者头像 李华
网站建设 2026/9/10 10:38:03

泰坦尼克号生存预测:从数据清洗到模型优化的完整指南

1. 项目背景与核心目标泰坦尼克号生存预测是机器学习领域最经典的入门项目之一,它基于1912年泰坦尼克号沉船事件中的乘客数据,要求我们构建模型预测每位乘客的生存概率。这个项目之所以成为机器学习教学的"Hello World",是因为它完…

作者头像 李华
网站建设 2026/9/10 10:35:33

CANN/ge捕获张量API

CaptureTensor 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华