尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python 可维护性设计模式实战:KISS、单一职责与组合优于继承(agents 项目 python-design-patterns 技能全解析)

Python 可维护性设计模式实战:KISS、单一职责与组合优于继承(agents 项目 python-design-patterns 技能全解析) Python 可维护性设计模式实战KISS、单一职责与组合优于继承agents 项目 python-design-patterns 技能全解析【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents本指南围绕 agents 仓库中 python-design-patterns 技能 及其详细模式文档展开系统讲解 KISS、单一职责SRP、关注点分离、组合优于继承、三的法则等核心设计原则。读完本文你将掌握一套可直接落地的 Python 代码分层、依赖注入与抽象决策方法学会在过早抽象与错误重复之间做出有依据的选择并能在代码评审中快速识别紧耦合、职责混杂与内部类型泄漏等结构性问题。一、技能概览何时使用这套设计模式该技能定位为面向新建组件或服务、重构复杂代码、以及评审结构性设计的决策框架。根据 SKILL.md 的声明它适用于以下六类典型场景从零设计新组件或服务时决定如何分层与分配职责重构已经膨胀的上帝类God class或巨型函数时判断是否值得新增一层抽象还是容忍当前的重复评审 Pull Request时识别紧耦合、内部类型泄漏等结构性问题抉择新的类层级应该用继承还是组合规划模块化架构时作为分层与职责划分的依据。仓库中配套的 python-pro Agent 将这套技能与 Python 3.12 现代特性、SOLID 原则、依赖注入、插件化架构等内容结合使用作为其生产级 Python 开发能力的一部分而 python-scaffold 命令则负责在项目初始化时落地对应的目录骨架。二、技能在仓库中的组织方式SKILL.md 导航 references 详解本技能采用两级文档结构SKILL.md提供核心概念、快速上手、最佳实践摘要与故障排查更详细的模式与完整代码示例存放在references/details.md。这种组织方式并非随意设计——仓库的 doc_gardener.py 工具明确约束了技能目录的规范它在plugins/*/skills/*/SKILL.md路径上遍历校验每个技能文件doc_gardener.py当检测到SKILL.md中塞入过多细节时会给出修复建议将细节章节移入references/details.md让 SKILL.md 只承担导航职责doc_gardener.py。也就是说读者应先用SKILL.md建立全局认知当导航层不足以支撑实践时再深入references/details.md查阅完整模式与可运行代码。这本身就是一个关注点分离的元实践。三、四大核心概念1. KISSKeep It Simple选择能工作的最简方案。任何复杂性都必须由具体需求来证明而不是由未来可能用到来假设。2. 单一职责原则SRP每个单元类或函数应当只有一个变更理由。把不同关注点拆分到各自聚焦的组件中。3. 组合优于继承通过组合对象来构建行为而不是通过扩展类层级。组合让依赖可替换、行为可裁剪、测试更简单。4. 三的法则Rule of Three在出现三次重复之前不要急于抽象。重复本身往往比过早的抽象更好——后者会把一个看似相似、实则不同的模式固化成错误的设计。四、快速上手Simple Beats Clever技能给出的第一个示范非常克制不要为了用模式而用模式。以下是一个常见的工厂注册器写法与一个普通字典的对比# Simple beats clever # Instead of a factory/registry pattern: FORMATTERS {json: JsonFormatter, csv: CsvFormatter} def get_formatter(name: str) - Formatter: return FORMATTERS[name]()在references/details.md的 Pattern 1 中这段对比被展开为完整的过度工程 vs 简单方案# 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(fUnknown format: {name}) return FORMATTERS[name]()工厂模式在这里增加了代码量却没有增加价值。字典天然支持查找与替换配合ValueError快速失败即可覆盖非法格式输入。把模式留到它能解决真实问题时再使用——这是整套技能反复强调的立场也是它与 python-anti-patterns 技能专注避免什么形成互补关系的出发点。五、基础模式详解Pattern 1–4Pattern 2单一职责原则 —— Handler 不再做所有事最典型的 SRP 反例是一个 handler 函数处理 HTTP 解析、业务校验、数据库访问、响应格式化四件事# 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}, status400) # 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}, status201)修正方式是拆成业务逻辑只属于 Service、HTTP 只属于 Handler两个类# 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(emaildata.email, namedata.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(), status201)效果是HTTP 层的变化不会波及业务逻辑业务逻辑的演化也不会污染 HTTP 层。两个类各自只有一个变更理由正好对应 SRP 的定义。值得注意的是UserHandler通过构造函数接收UserService而不是在内部直接实例化——这正是后续 Pattern 7 依赖注入的雏形。Pattern 3关注点分离 —— 三层架构与依赖方向关注点分离把代码组织为三个职责清晰的层级依赖箭头严格向下┌─────────────────────────────────────────────────────┐ │ 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 │ └─────────────────────────────────────────────────────┘每层只依赖其下方的层配对的完整代码如下# 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 了 API 层的 handler就构成了分层违规详见本文第九节。同一分层思路在 python-project-structure 技能 中被固化为目录结构——api/、services/、repositories/、models/、schemas/各司其职每层只依赖下层、绝不依赖上层。Pattern 4组合优于继承 —— 通知服务的改造先看继承方案的问题# 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)SmtpClient在__init__内部被直接实例化测试时难以替换。组合方案把每个发送通道作为构造参数注入并支持按需组合# 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_senderFakeEmailSender(), sms_senderFakeSmsSender(), )组合优于继承的本质收益通道可插拔、缺失通道不报错None即可关闭、每个依赖都可替换为 fake。这与 python-testing-patterns 技能 强调的用隔离测试逐层验证、用 Mock 替换外部依赖形成了直接的配合关系——组合结构正是可测试性的前提。六、进阶模式详解Pattern 5–8Pattern 5三的法则 —— 何时才值得抽象两个看似相似的函数不代表就应该立刻合并# Two similar functions? Dont 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 theres a real pattern # But even then, sometimes explicit is better than abstractprocess_orders与process_returns结构相似但校验逻辑、处理逻辑、错误类型都不同。在第三个实例出现之前重复优于错误的抽象即便出现第三个实例也仍要评估显式表达是否优于统一抽象。这条启发式在故障排查章节还有重要补充当重复已经以危险的方式产生分歧改了一处没改另一处导致 bug时应立即抽取并补上覆盖共享行为的测试——三的法则不是法律是启发。Pattern 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(successTrue, order_idorder.id)重构后的process_order变成一段可朗读的编排代码每一步都是一个有明确名称、单一职责的小函数。这与 python-project-structure 技能 中单文件单概念、文件超过 300–500 行考虑拆分的准则属于同一哲学只是作用粒度从文件下探到函数。Pattern 7依赖注入 —— 构造器注入与 Protocol把依赖通过构造器传入是保障可测试性的核心手段。技能使用typing.Protocol为缓存与日志声明最小结构接口使依赖可替换且类型安全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(fuser:{user_id}) if cached: self._logger.info(Cache hit, user_iduser_id) return User.from_json(cached) # Fetch from database user await self._repo.get_by_id(user_id) if user: await self._cache.set(fuser:{user_id}, user.to_json(), ttl300) return user同一份UserService在生产环境与测试环境只需更换构造参数# Production service UserService( repositoryPostgresUserRepository(db), cacheRedisCache(redis), loggerStructlogLogger(), ) # Testing service UserService( repositoryInMemoryUserRepository(), cacheFakeCache(), loggerNullLogger(), )使用Protocol而非抽象基类定义接口是 Python 特有的结构化类型风格任何恰好具有这些方法的对象都可以满足接口无需继承。这正是 Pattern 4组合优于继承在类型层面的延伸。关于 Protocol、泛型与T | None联合类型语法的更多细节可参考同目录下的 python-type-safety 技能。Pattern 8规避常见反模式不要向 API 层暴露内部类型# 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)不要混入 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把数据访问下沉到 Repositorycalculate_discount变成纯函数——同样的输入永远得到同样的输出测试无需数据库。这两条反模式暴露内部类型、混合 I/O 与逻辑在 python-anti-patterns 技能 中被列为架构反模式并附有逐条修复对照表该技能还额外覆盖了散落的超时/重试、裸except Exception: pass、批处理首错即停、未关闭资源、async 中阻塞调用、缺少类型标注等基础设施与资源层面的反模式可作为本技能正向模式的镜像清单配套使用。七、最佳实践十条摘要SKILL.md 将整套方法论浓缩为十条可执行规则Keep it simple—— 选择能工作的最简方案Single responsibility—— 每个单元只有一个变更理由Separate concerns—— 分层明确、职责清晰的架构Compose, dont inherit—— 组合对象换取灵活性Rule of three—— 出现三次重复再考虑抽象Keep functions small—— 20–50 行视复杂度浮动单一目的Inject dependencies—— 构造器注入换取可测试性Delete before abstracting—— 先删除死代码再考虑引入模式Test each layer—— 每个关注点都有隔离测试Explicit over clever—— 可读的代码胜过优雅的代码。其中第 9 条直接指向 python-testing-patterns 技能在依赖注入结构建立之后用 AAAArrange-Act-Assert模式、fixture 与 Mock 逐层编写隔离测试第 3 条指向 python-project-structure 技能用目录布局让分层边界从项目第一天就显式存在。八、故障排查指南五个常见决策困境技能专门为实践中反复出现的五类两难给出了明确裁决标准。1. 一个类似乎在膨胀、承担了多重职责但拆分它感觉不对执行变更理由测试列出所有可能需要修改这个类的变更。如果清单横跨不同领域比如既有 HTTP 解析、又有业务规则、还有格式化就拆。如果所有变更都源于同一个领域关注点那么这个类的体量可能是恰当的。2. 构造器注入导致构造参数达到 7 个以上这是一个类承担了太多职责的信号而不是依赖注入本身的问题。先把类拆小每个新构造器自然就会变小。结合 Pattern 7 中Logger、Cache这类 Protocol 的使用可以进一步用参数分组压缩参数个数但根因仍是职责过载。3. 组合产生了难以追踪的深度嵌套包装对象把组合深度控制在 2–3 层。如果包装是唯一机制考虑用 Protocol 接口或简单函数组合替代装饰器对象链。这与 Pattern 7 的结构化接口优先于继承层级一脉相承。4. 三的法则说先别抽象但重复已经造成 bug改了一处漏了另一处以危险方式分叉的重复应当尽早抽象。三的法则只是启发而非法律——如果副本已经出现错误分歧立即抽取并添加覆盖共享行为的测试。5. Service 层 import 了 API 层破坏了依赖方向这是分层违规。Service 层绝不允许 import handler。应引入一个两者都能 import 的共享类型/模型层让依赖箭头保持向下API → Service → Repository。九、与相邻技能的协同使用这套设计模式并非孤立存在仓库中围绕它构建了一组互补技能python-testing-patterns利用本技能建立的依赖注入结构逐层隔离测试覆盖重试行为、时间冻结freezegun、测试标记与覆盖率门槛python-project-structure从目录与模块层面落实分层边界、__all__显式公共接口与扁平结构python-anti-patterns作为避免什么的镜像清单与本技能的正向模式配合形成完整的评审闭环python-type-safety为组合与注入提供类型层面的支撑——Protocol 结构化接口、泛型与类型收窄python-error-handling为分层架构提供异常策略——边界处快速失败、异常映射到标准类型、批量操作容忍部分失败。十、总结python-design-patterns 技能的核心立场可以概括为一句话优先编写简单、可读、可测试的代码把模式与抽象当作被需求证明后的选择而不是默认动作。KISS 与三的法则约束了抽象的时机SRP 与关注点分离约束了职责的边界组合优于继承与依赖注入约束了结构的形式而先删死代码再谈模式显式优于聪明则为每一次设计决策提供了可操作的检验标准。无论你是从零设计新服务、重构遗留巨型类还是在评审中审视耦合与泄漏这套框架都能给出明确、可辩护的裁决依据——这正是它在 agents 仓库中被定位为 Python 开发核心技能的原因。【免费下载链接】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),仅供参考
返回列表