@umacloud/knowledge 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/00-governance/governance-capabilities.md +557 -0
- package/00-governance/knowledge-map.md +39 -0
- package/00-governance/maintenance-policy.md +76 -0
- package/00-governance/review-checklist.md +81 -0
- package/README.md +13 -0
- package/ai/01-standards/agent-development-complete.md +691 -0
- package/ai/01-standards/llm-application-complete.md +488 -0
- package/ai/01-standards/mlops-complete.md +798 -0
- package/ai/01-standards/prompt-engineering-complete.md +646 -0
- package/ai/01-standards/rag-architecture-complete.md +649 -0
- package/ai/02-playbooks/llm-evaluation-playbook.md +847 -0
- package/ai/03-checklists/ai-project-checklist.md +215 -0
- package/ai/04-antipatterns/ai-antipatterns.md +661 -0
- package/ai/05-cases/case-rag-production.md +147 -0
- package/ai/06-glossary/ai-glossary.md +162 -0
- package/ai/agent-evaluation-benchmark.md +53 -0
- package/ai/ai-agent-memory-context-management.md +41 -0
- package/ai/ai-cost-capacity-optimization-playbook.md +42 -0
- package/ai/ai-data-security-and-compliance-playbook.md +37 -0
- package/ai/ai-domain-index-and-checklist.md +40 -0
- package/ai/ai-governance-maturity-model.md +50 -0
- package/ai/ai-model-selection-and-routing-strategy.md +47 -0
- package/ai/ai-observability-and-oncall-runbook.md +52 -0
- package/ai/ai-rag-engineering-playbook.md +42 -0
- package/ai/ai-red-team-and-safety-evaluation.md +42 -0
- package/ai/ai-release-readiness-and-rollback-gate.md +42 -0
- package/ai/llm-agent-engineering-deep-dive.md +57 -0
- package/ai/prompt-and-tool-guardrails.md +52 -0
- package/api/01-standards/enterprise-api-standards.md +198 -0
- package/api/01-standards/rest-api-design-guide.md +63 -0
- package/api/02-playbooks/api-pagination-playbook.md +93 -0
- package/api/02-playbooks/graphql-production-playbook.md +176 -0
- package/api/03-checklists/api-review-checklist.md +55 -0
- package/api/04-antipatterns/api-antipatterns.md +112 -0
- package/architecture/01-standards/api-gateway-patterns.md +496 -0
- package/architecture/01-standards/cloud-native-patterns.md +644 -0
- package/architecture/01-standards/distributed-systems-patterns.md +591 -0
- package/architecture/01-standards/event-driven-architecture.md +595 -0
- package/architecture/01-standards/microservices-patterns-complete.md +968 -0
- package/architecture/01-standards/microservices-patterns.md +495 -0
- package/architecture/01-standards/system-design-interview.md +664 -0
- package/architecture/02-playbooks/microservices-patterns-playbook.md +137 -0
- package/architecture/02-playbooks/migration-playbook.md +780 -0
- package/architecture/02-playbooks/system-design-playbook.md +779 -0
- package/architecture/03-checklists/architecture-decision-checklist.md +297 -0
- package/architecture/04-antipatterns/architecture-antipatterns.md +417 -0
- package/architecture/05-cases/case-netflix-microservices.md +413 -0
- package/architecture/06-glossary/architecture-glossary.md +164 -0
- package/architecture/adr-template-and-examples.md +38 -0
- package/architecture/api-gateway-deep-dive.md +1291 -0
- package/architecture/configuration-management.md +1162 -0
- package/architecture/distributed-transactions.md +1220 -0
- package/architecture/microservices-complete.md +735 -0
- package/architecture/resilience-and-disaster-patterns.md +37 -0
- package/architecture/service-governance.md +1198 -0
- package/architecture/system-architecture-deep-dive.md +37 -0
- package/backend/01-standards/analytics-and-growth.md +65 -0
- package/backend/01-standards/api-and-error-conventions.md +120 -0
- package/backend/01-standards/application-layering-and-packaging.md +160 -0
- package/backend/01-standards/auth-implementation.md +104 -0
- package/backend/01-standards/backend-framework-idioms.md +74 -0
- package/backend/01-standards/background-jobs-and-async.md +66 -0
- package/backend/01-standards/caching-strategies-complete.md +390 -0
- package/backend/01-standards/config-and-observability.md +77 -0
- package/backend/01-standards/data-modeling-and-persistence.md +94 -0
- package/backend/01-standards/django-complete.md +1765 -0
- package/backend/01-standards/email-and-notifications.md +64 -0
- package/backend/01-standards/fastapi-complete.md +925 -0
- package/backend/01-standards/file-upload-and-storage.md +66 -0
- package/backend/01-standards/graphql-api-complete.md +416 -0
- package/backend/01-standards/llm-application-standard.md +78 -0
- package/backend/01-standards/message-queue-patterns.md +379 -0
- package/backend/01-standards/microservices-and-distributed.md +78 -0
- package/backend/01-standards/nestjs-complete.md +2167 -0
- package/backend/01-standards/payment-integration.md +80 -0
- package/backend/01-standards/rate-limiting-complete.md +451 -0
- package/backend/01-standards/realtime-and-websocket.md +65 -0
- package/backend/01-standards/search-and-filtering.md +64 -0
- package/backend/01-standards/spring-boot-complete.md +445 -0
- package/backend/02-playbooks/api-design-playbook.md +718 -0
- package/backend/02-playbooks/email-send-playbook.md +130 -0
- package/backend/02-playbooks/file-upload-s3-playbook.md +153 -0
- package/backend/02-playbooks/typescript-enterprise-playbook.md +133 -0
- package/backend/02-playbooks/websocket-realtime-playbook.md +154 -0
- package/backend/03-checklists/api-launch-checklist.md +189 -0
- package/backend/04-antipatterns/backend-antipatterns.md +1051 -0
- package/blockchain/01-standards/blockchain-basics.md +557 -0
- package/blockchain/01-standards/smart-contract-development.md +1315 -0
- package/cicd/01-standards/deployment-and-delivery-standard.md +96 -0
- package/cicd/01-standards/github-actions-complete.md +473 -0
- package/cicd/01-standards/release-and-store-submission.md +75 -0
- package/cicd/02-playbooks/cicd-pipeline-playbook.md +144 -0
- package/cicd/02-playbooks/release-management-playbook.md +605 -0
- package/cicd/03-checklists/pipeline-security-checklist.md +168 -0
- package/cicd/04-antipatterns/cicd-antipatterns.md +589 -0
- package/cicd/05-cases/case-deployment-automation.md +221 -0
- package/cicd/05-cases/case-gitops-transformation.md +212 -0
- package/cicd/06-glossary/cicd-glossary.md +114 -0
- package/cicd/cicd-blueprint-deep-dive.md +38 -0
- package/cicd/release-readiness-gate.md +37 -0
- package/cloud-native/01-standards/container-security.md +741 -0
- package/cloud-native/01-standards/kubernetes-complete.md +812 -0
- package/cloud-native/02-playbooks/api-gateway-playbook.md +155 -0
- package/cloud-native/02-playbooks/gitops-with-argocd.md +760 -0
- package/cloud-native/02-playbooks/k8s-troubleshooting-playbook.md +1942 -0
- package/cloud-native/02-playbooks/message-queue-playbook.md +129 -0
- package/cloud-native/02-playbooks/multicloud-governance.md +726 -0
- package/cloud-native/02-playbooks/serverless-patterns.md +788 -0
- package/cloud-native/02-playbooks/service-mesh-playbook.md +612 -0
- package/cloud-native/02-playbooks/terraform-iac-playbook.md +143 -0
- package/cloud-native/03-checklists/container-security-checklist.md +431 -0
- package/cloud-native/03-checklists/k8s-production-readiness-checklist.md +460 -0
- package/cloud-native/04-antipatterns/container-antipatterns.md +660 -0
- package/cloud-native/04-antipatterns/k8s-antipatterns.md +743 -0
- package/cloud-native/05-cases/case-k8s-migration.md +478 -0
- package/cloud-native/05-cases/case-k8s-scaling.md +642 -0
- package/cloud-native/05-cases/case-k8s-security-incident.md +397 -0
- package/cloud-native/06-glossary/cloud-native-glossary.md +337 -0
- package/cross-platform/01-standards/cross-platform-frameworks.md +83 -0
- package/cross-platform/01-standards/platform-selection-and-architecture.md +77 -0
- package/data/01-standards/elasticsearch-complete.md +2098 -0
- package/data/01-standards/postgresql-complete.md +1613 -0
- package/data/01-standards/redis-complete.md +1527 -0
- package/data/02-playbooks/database-optimization-playbook.md +403 -0
- package/data/02-playbooks/elasticsearch-production-playbook.md +132 -0
- package/data/03-checklists/database-launch-checklist.md +187 -0
- package/data/04-antipatterns/database-antipatterns.md +873 -0
- package/data/05-cases/case-database-migration.md +310 -0
- package/data/06-glossary/database-glossary.md +440 -0
- package/data/data-governance-and-modeling-deep-dive.md +39 -0
- package/data-engineering/01-standards/airflow-complete.md +523 -0
- package/data-engineering/01-standards/kafka-complete.md +1521 -0
- package/data-engineering/02-playbooks/spark-etl-playbook.md +496 -0
- package/data-engineering/03-checklists/pipeline-launch-checklist.md +194 -0
- package/data-engineering/04-antipatterns/data-pipeline-antipatterns.md +684 -0
- package/data-engineering/05-cases/case-real-time-pipeline.md +355 -0
- package/data-engineering/06-glossary/data-engineering-glossary.md +429 -0
- package/database/01-standards/database-schema-standards.md +147 -0
- package/database/02-playbooks/postgresql-optimization-quick.md +52 -0
- package/database/02-playbooks/postgresql-performance-optimization.md +58 -0
- package/database/02-playbooks/postgresql-production-playbook.md +146 -0
- package/database/02-playbooks/redis-caching-playbook.md +117 -0
- package/database/03-checklists/database-review-checklist.md +50 -0
- package/database/04-antipatterns/database-antipatterns.md +112 -0
- package/design/01-standards/ui-design-system-complete.md +423 -0
- package/design/02-playbooks/design-handoff-playbook.md +254 -0
- package/design/02-playbooks/design-review-playbook.md +388 -0
- package/design/03-checklists/design-review-checklist.md +246 -0
- package/design/04-antipatterns/design-antipatterns.md +378 -0
- package/design/05-cases/case-design-system-adoption.md +328 -0
- package/design/06-glossary/design-glossary.md +329 -0
- package/design/ui-full-lifecycle-cross-platform-playbook.md +571 -0
- package/design/ux-system-deep-dive.md +38 -0
- package/design-systems/00-craft-rules.md +71 -0
- package/design-systems/aesthetic-families.md +43 -0
- package/design-systems/anti-ai-slop.md +162 -0
- package/design-systems/bold-geometric.md +120 -0
- package/design-systems/brutalist-bold.md +103 -0
- package/design-systems/editorial-clean.md +109 -0
- package/design-systems/glass-aurora.md +108 -0
- package/design-systems/modern-minimal.md +145 -0
- package/design-systems/premium-luxury.md +106 -0
- package/design-systems/product-type-design-map.md +48 -0
- package/design-systems/soft-warm.md +123 -0
- package/design-systems/tech-utility.md +113 -0
- package/desktop/01-standards/desktop-app-standard.md +72 -0
- package/desktop/01-standards/desktop-design.md +71 -0
- package/development/00-governance/document-template.md +41 -0
- package/development/01-standards/api-versioning-strategies.md +432 -0
- package/development/01-standards/authentication-patterns-complete.md +479 -0
- package/development/01-standards/css-architecture-complete.md +550 -0
- package/development/01-standards/database-migration-strategies.md +484 -0
- package/development/01-standards/elasticsearch-complete.md +347 -0
- package/development/01-standards/git-complete.md +371 -0
- package/development/01-standards/golang-complete.md +1565 -0
- package/development/01-standards/graphql-complete.md +298 -0
- package/development/01-standards/javascript-bundlers-complete.md +469 -0
- package/development/01-standards/javascript-typescript-complete.md +528 -0
- package/development/01-standards/jest-complete.md +275 -0
- package/development/01-standards/linux-complete.md +234 -0
- package/development/01-standards/logging-observability-complete.md +526 -0
- package/development/01-standards/microservices-communication.md +502 -0
- package/development/01-standards/mongodb-complete.md +406 -0
- package/development/01-standards/oauth2-complete.md +285 -0
- package/development/01-standards/performance-optimization-complete.md +289 -0
- package/development/01-standards/playwright-complete.md +247 -0
- package/development/01-standards/postgresql-complete.md +456 -0
- package/development/01-standards/pytest-complete.md +340 -0
- package/development/01-standards/python-async-programming.md +902 -0
- package/development/01-standards/python-complete.md +956 -0
- package/development/01-standards/python-decorators-complete.md +799 -0
- package/development/01-standards/python-design-patterns.md +2854 -0
- package/development/01-standards/python-packaging-distribution.md +420 -0
- package/development/01-standards/python-testing-strategies.md +607 -0
- package/development/01-standards/python-web-frameworks-comparison.md +471 -0
- package/development/01-standards/redis-complete.md +317 -0
- package/development/01-standards/rest-api-complete.md +316 -0
- package/development/01-standards/rust-complete.md +578 -0
- package/development/01-standards/typescript-advanced-types.md +1513 -0
- package/development/01-standards/web-security-complete.md +292 -0
- package/development/02-playbooks/api-design-playbook.md +810 -0
- package/development/02-playbooks/database-migration-playbook.md +580 -0
- package/development/02-playbooks/debugging-playbook.md +692 -0
- package/development/02-playbooks/feature-delivery-playbook.md +430 -0
- package/development/02-playbooks/incident-hotfix-playbook.md +387 -0
- package/development/02-playbooks/performance-optimization-playbook.md +531 -0
- package/development/02-playbooks/performance-tuning-playbook.md +652 -0
- package/development/02-playbooks/refactor-playbook.md +403 -0
- package/development/02-playbooks/release-playbook.md +469 -0
- package/development/03-checklists/architecture-review-checklist.md +168 -0
- package/development/03-checklists/data-migration-checklist.md +157 -0
- package/development/03-checklists/oncall-handover-checklist.md +173 -0
- package/development/03-checklists/pr-checklist.md +158 -0
- package/development/03-checklists/production-readiness-checklist.md +190 -0
- package/development/03-checklists/release-readiness-checklist.md +154 -0
- package/development/03-checklists/security-review-checklist.md +182 -0
- package/development/04-antipatterns/api-antipatterns.md +657 -0
- package/development/04-antipatterns/architecture-antipatterns.md +686 -0
- package/development/04-antipatterns/backend-antipatterns.md +648 -0
- package/development/04-antipatterns/cicd-antipatterns.md +540 -0
- package/development/04-antipatterns/code-smell-antipatterns.md +571 -0
- package/development/04-antipatterns/data-antipatterns.md +658 -0
- package/development/04-antipatterns/database-antipatterns.md +578 -0
- package/development/04-antipatterns/frontend-antipatterns.md +635 -0
- package/development/04-antipatterns/reliability-antipatterns.md +700 -0
- package/development/04-antipatterns/security-antipatterns.md +747 -0
- package/development/05-cases/case-api-version-migration.md +428 -0
- package/development/05-cases/case-authorization-hardening.md +383 -0
- package/development/05-cases/case-bluegreen-rollback.md +466 -0
- package/development/05-cases/case-cache-snowball-protection.md +485 -0
- package/development/05-cases/case-ci-cd-pipeline.md +544 -0
- package/development/05-cases/case-database-scaling.md +500 -0
- package/development/05-cases/case-db-hotspot-optimization.md +487 -0
- package/development/05-cases/case-incident-mttr-reduction.md +563 -0
- package/development/05-cases/case-microservice-migration.md +375 -0
- package/development/05-cases/case-performance-optimization.md +406 -0
- package/development/05-cases/case-security-incident-response.md +345 -0
- package/development/06-glossary/full-stack-glossary.md +166 -0
- package/development/09-maturity/quarterly-audit-template.md +35 -0
- package/development/11-ui-excellence/ui-aesthetic-system.md +41 -0
- package/development/11-ui-excellence/ui-engineering-excellence.md +435 -0
- package/development/12-scenarios/development-scenarios-guide.md +565 -0
- package/development/13-implementation-assets/implementation-toolkit.md +282 -0
- package/development/13-implementation-assets/knowledge-gates-execution.md +43 -0
- package/development/14-full-lifecycle/software-lifecycle-gates.md +511 -0
- package/development/15-lifecycle-templates/project-templates-collection.md +791 -0
- package/development/api-contract-and-versioning-guide.md +36 -0
- package/development/api-governance-complete.md +43 -0
- package/development/backend-engineering-complete.md +43 -0
- package/development/code-review-quality-complete.md +43 -0
- package/development/concurrency-reliability-complete.md +43 -0
- package/development/database-engineering-complete.md +43 -0
- package/development/engineering-effectiveness-complete.md +43 -0
- package/development/engineering-standards-deep-dive.md +38 -0
- package/development/frontend-engineering-complete.md +43 -0
- package/development/performance-capacity-complete.md +43 -0
- package/development/refactor-migration-complete.md +42 -0
- package/development/refactoring-and-techdebt-playbook.md +37 -0
- package/development/security-in-development-complete.md +43 -0
- package/devops/01-standards/cicd-pipeline-complete.md +262 -0
- package/devops/01-standards/docker-complete.md +1490 -0
- package/devops/01-standards/github-actions-complete.md +337 -0
- package/devops/01-standards/kubernetes-complete.md +638 -0
- package/devops/01-standards/terraform-complete.md +2117 -0
- package/devops/02-playbooks/docker-compose-playbook.md +233 -0
- package/devops/02-playbooks/docker-k8s-production-playbook.md +186 -0
- package/devops/02-playbooks/docker-production-playbook.md +952 -0
- package/edge-iot/01-standards/edge-iot-complete.md +473 -0
- package/experts/architect/api-design.md +178 -0
- package/experts/architect/methodology.md +124 -0
- package/experts/architect/security.md +75 -0
- package/experts/backend-lead/methodology.md +216 -0
- package/experts/devops/methodology.md +160 -0
- package/experts/frontend-lead/methodology.md +178 -0
- package/experts/product-manager/industry/ecommerce.md +43 -0
- package/experts/product-manager/industry/saas.md +40 -0
- package/experts/product-manager/methodology.md +97 -0
- package/experts/qa-lead/methodology.md +123 -0
- package/experts/qa-lead/test-strategy.md +128 -0
- package/experts/uiux-designer/methodology.md +125 -0
- package/frontend/01-standards/accessibility-complete.md +532 -0
- package/frontend/01-standards/accessibility-standard.md +74 -0
- package/frontend/01-standards/admin-dashboard-and-crud.md +72 -0
- package/frontend/01-standards/design-tokens-complete.md +444 -0
- package/frontend/01-standards/forms-and-validation.md +77 -0
- package/frontend/01-standards/frontend-architecture-and-layering.md +119 -0
- package/frontend/01-standards/i18n-and-localization.md +65 -0
- package/frontend/01-standards/nextjs-complete.md +451 -0
- package/frontend/01-standards/react-complete.md +713 -0
- package/frontend/01-standards/react-hooks-complete-guide.md +1100 -0
- package/frontend/01-standards/react-hooks-complete.md +1171 -0
- package/frontend/01-standards/seo-and-web-vitals.md +77 -0
- package/frontend/01-standards/state-management-complete.md +444 -0
- package/frontend/01-standards/vue-complete.md +499 -0
- package/frontend/01-standards/vue3-complete.md +2002 -0
- package/frontend/01-standards/web-framework-best-practices.md +64 -0
- package/frontend/01-standards/web-performance-complete.md +495 -0
- package/frontend/02-playbooks/accessibility-a11y-playbook.md +161 -0
- package/frontend/02-playbooks/frontend-performance-playbook.md +707 -0
- package/frontend/02-playbooks/i18n-internationalization-playbook.md +120 -0
- package/frontend/02-playbooks/performance-optimization-playbook.md +163 -0
- package/frontend/02-playbooks/react-nextjs-production-playbook.md +167 -0
- package/frontend/02-playbooks/react-state-management-playbook.md +173 -0
- package/frontend/03-checklists/component-quality-checklist.md +166 -0
- package/frontend/03-checklists/frontend-launch-checklist.md +299 -0
- package/frontend/04-antipatterns/frontend-antipatterns.md +886 -0
- package/frontend/05-cases/case-performance-optimization.md +274 -0
- package/harmony/01-standards/harmonyos-arkts-standard.md +75 -0
- package/harmony/01-standards/harmonyos-design.md +65 -0
- package/high-quality-engineering-playbook.md +54 -0
- package/incident/01-standards/incident-response-complete.md +303 -0
- package/incident/02-playbooks/chaos-engineering-playbook.md +883 -0
- package/incident/02-playbooks/postmortem-playbook.md +398 -0
- package/incident/03-checklists/incident-readiness-checklist.md +181 -0
- package/incident/04-antipatterns/incident-antipatterns.md +490 -0
- package/incident/05-cases/case-cascade-failure.md +176 -0
- package/incident/06-glossary/incident-glossary.md +114 -0
- package/incident/postmortem-and-response-deep-dive.md +39 -0
- package/industries/ecommerce/ecommerce-complete.md +631 -0
- package/industries/education/education-complete.md +555 -0
- package/industries/fintech/fintech-complete.md +501 -0
- package/industries/gaming/gaming-complete.md +587 -0
- package/industries/healthcare/healthcare-complete.md +452 -0
- package/low-code/01-standards/low-code-complete.md +944 -0
- package/miniprogram/01-standards/ai-common-mistakes.md +61 -0
- package/miniprogram/01-standards/miniprogram-custom-navbar-capsule.md +77 -0
- package/miniprogram/01-standards/miniprogram-design.md +61 -0
- package/miniprogram/01-standards/miniprogram-standard.md +81 -0
- package/mobile/01-standards/android-material-design.md +70 -0
- package/mobile/01-standards/flutter-complete.md +384 -0
- package/mobile/01-standards/ios-design-hig.md +78 -0
- package/mobile/01-standards/mobile-app-standard.md +85 -0
- package/mobile/01-standards/react-native-complete.md +352 -0
- package/mobile/02-playbooks/mobile-cross-platform-playbook.md +175 -0
- package/mobile/02-playbooks/mobile-performance.md +473 -0
- package/mobile/03-checklists/mobile-release-checklist.md +234 -0
- package/mobile/04-antipatterns/mobile-antipatterns.md +798 -0
- package/mobile/05-cases/case-app-performance.md +500 -0
- package/mobile/05-cases/case-app-startup-optimization.md +218 -0
- package/mobile/06-glossary/mobile-glossary.md +484 -0
- package/observability/01-standards/observability-standards.md +103 -0
- package/observability/02-playbooks/prometheus-grafana-playbook.md +135 -0
- package/observability/02-playbooks/structured-logging-playbook.md +73 -0
- package/observability/03-checklists/observability-checklist.md +54 -0
- package/observability/04-antipatterns/observability-antipatterns.md +106 -0
- package/operations/01-standards/prometheus-monitoring-complete.md +1578 -0
- package/operations/02-playbooks/capacity-planning-playbook.md +620 -0
- package/operations/03-checklists/production-launch-checklist.md +365 -0
- package/operations/04-antipatterns/operations-antipatterns.md +664 -0
- package/operations/05-cases/case-sre-practices.md +581 -0
- package/operations/06-glossary/operations-glossary.md +120 -0
- package/operations/aiops-anomaly-detection.md +758 -0
- package/operations/capacity-planning.md +1061 -0
- package/operations/chaos-engineering.md +659 -0
- package/operations/incident-command-system.md +38 -0
- package/operations/observability-complete.md +442 -0
- package/operations/slo-sli-playbook.md +517 -0
- package/operations/sre-operations-deep-dive.md +39 -0
- package/package.json +8 -0
- package/performance/01-standards/performance-and-scalability.md +80 -0
- package/performance/01-standards/performance-standards.md +156 -0
- package/performance/02-playbooks/query-optimization-playbook.md +103 -0
- package/performance/03-checklists/performance-checklist.md +56 -0
- package/performance/04-antipatterns/performance-antipatterns.md +146 -0
- package/product/01-standards/product-management-complete.md +285 -0
- package/product/02-playbooks/feature-launch-playbook.md +207 -0
- package/product/02-playbooks/user-research-playbook.md +532 -0
- package/product/03-checklists/feature-launch-checklist.md +275 -0
- package/product/04-antipatterns/product-antipatterns.md +355 -0
- package/product/05-cases/case-mvp-to-scale.md +384 -0
- package/product/06-glossary/product-glossary.md +462 -0
- package/product/feature-prioritization-framework.md +40 -0
- package/product/kpi-and-metric-tree.md +37 -0
- package/product/product-discovery-and-prd-deep-dive.md +41 -0
- package/quantum/01-standards/quantum-complete.md +1186 -0
- package/security/01-standards/api-security-complete.md +511 -0
- package/security/01-standards/container-runtime-security.md +574 -0
- package/security/01-standards/data-protection-gdpr.md +543 -0
- package/security/01-standards/owasp-top10-complete.md +1890 -0
- package/security/01-standards/secure-coding-baseline.md +90 -0
- package/security/01-standards/supply-chain-security.md +441 -0
- package/security/01-standards/web-security-checklist.md +108 -0
- package/security/01-standards/zero-trust-architecture.md +521 -0
- package/security/02-playbooks/auth-sso-playbook.md +166 -0
- package/security/02-playbooks/incident-response-security-playbook.md +588 -0
- package/security/02-playbooks/owasp-api-security-playbook.md +129 -0
- package/security/02-playbooks/payment-integration-playbook.md +119 -0
- package/security/02-playbooks/penetration-testing-playbook.md +517 -0
- package/security/03-checklists/security-audit-checklist.md +356 -0
- package/security/04-antipatterns/security-coding-antipatterns.md +580 -0
- package/security/05-cases/case-log4shell-incident.md +537 -0
- package/security/05-cases/case-major-breaches.md +468 -0
- package/security/06-glossary/security-glossary.md +212 -0
- package/security/compliance-automation.md +993 -0
- package/security/container-security.md +680 -0
- package/security/devsecops-complete.md +426 -0
- package/security/sast-dast-sca.md +775 -0
- package/security/secrets-management.md +594 -0
- package/security/security-architecture-deep-dive.md +37 -0
- package/security/threat-modeling-stride-playbook.md +40 -0
- package/seed-templates/auth-system.md +59 -0
- package/seed-templates/blog-content.md +94 -0
- package/seed-templates/dashboard.md +89 -0
- package/seed-templates/docs-site.md +73 -0
- package/seed-templates/e-commerce.md +50 -0
- package/seed-templates/saas-landing.md +92 -0
- package/seed-templates/settings-page.md +51 -0
- package/testing/01-standards/test-strategy-and-layering.md +83 -0
- package/testing/01-standards/testing-strategy-complete.md +422 -0
- package/testing/01-standards/unit-testing-best-practices.md +118 -0
- package/testing/02-playbooks/e2e-testing-playbook.md +988 -0
- package/testing/02-playbooks/testing-strategy-playbook.md +126 -0
- package/testing/03-checklists/test-strategy-checklist.md +208 -0
- package/testing/04-antipatterns/testing-antipatterns.md +718 -0
- package/testing/05-cases/case-testing-transformation.md +300 -0
- package/testing/06-glossary/testing-glossary.md +110 -0
- package/testing/risk-based-test-matrix.md +36 -0
- package/testing/testing-strategy-deep-dive.md +37 -0
|
@@ -0,0 +1,2854 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: python-design-patterns
|
|
3
|
+
title: Python设计模式完整知识体系
|
|
4
|
+
domain: development
|
|
5
|
+
category: 01-standards
|
|
6
|
+
difficulty: intermediate
|
|
7
|
+
tags: [agent, checklist, design, development, patterns, python, python特有模式, 创建型模式]
|
|
8
|
+
quality_score: 70
|
|
9
|
+
last_updated: 2026-06-15
|
|
10
|
+
---
|
|
11
|
+
# Python设计模式完整知识体系
|
|
12
|
+
|
|
13
|
+
## 概述
|
|
14
|
+
|
|
15
|
+
设计模式是解决反复出现的软件设计问题的可复用方案。Python因其动态类型、一等函数、鸭子类型和丰富的内置协议,使得许多传统GoF模式在Python中有更简洁、更惯用的实现方式。
|
|
16
|
+
|
|
17
|
+
### Python设计模式的特殊性
|
|
18
|
+
|
|
19
|
+
- **一等函数替代策略/命令类层次**: Python函数是一等公民,许多需要单方法接口的模式可直接用函数或callable对象实现
|
|
20
|
+
- **鸭子类型替代接口**: 无需显式接口声明,依赖协议(Protocol)和结构化子类型
|
|
21
|
+
- **装饰器语法糖**: Python内置的`@decorator`语法让装饰器模式成为语言特性
|
|
22
|
+
- **元类和描述符**: 提供Java/C++中不存在的元编程能力
|
|
23
|
+
- **上下文管理器协议**: `with`语句为资源管理提供标准化模式
|
|
24
|
+
- **dataclass和NamedTuple**: 极大简化值对象和数据传输对象的创建
|
|
25
|
+
|
|
26
|
+
### Python惯用法 vs 传统GoF模式
|
|
27
|
+
|
|
28
|
+
| GoF模式 | Java实现 | Python惯用实现 |
|
|
29
|
+
|---------|----------|---------------|
|
|
30
|
+
| 策略模式 | 接口 + 多个实现类 | 函数/callable传参 |
|
|
31
|
+
| 单例模式 | 双重检查锁 | 模块级变量/`__new__` |
|
|
32
|
+
| 迭代器模式 | Iterator接口 | `__iter__`/`__next__`/生成器 |
|
|
33
|
+
| 模板方法 | 抽象类 + 子类覆写 | 函数参数/hooks |
|
|
34
|
+
| 观察者模式 | Listener接口 | 信号/回调函数列表 |
|
|
35
|
+
| 装饰器模式 | 接口包装类 | `@decorator`语法 |
|
|
36
|
+
| 命令模式 | Command接口 + 实现 | callable对象/闭包 |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 创建型模式
|
|
41
|
+
|
|
42
|
+
### 1. 工厂方法模式 (Factory Method)
|
|
43
|
+
|
|
44
|
+
**问题描述**: 需要根据不同条件创建不同类型的对象,但调用方不应该依赖具体类。
|
|
45
|
+
|
|
46
|
+
**Python实现**:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from typing import Protocol, runtime_checkable
|
|
50
|
+
from dataclasses import dataclass
|
|
51
|
+
|
|
52
|
+
@runtime_checkable
|
|
53
|
+
class Notification(Protocol):
|
|
54
|
+
def send(self, message: str) -> bool: ...
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class EmailNotification:
|
|
58
|
+
recipient: str
|
|
59
|
+
|
|
60
|
+
def send(self, message: str) -> bool:
|
|
61
|
+
print(f"Email to {self.recipient}: {message}")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class SMSNotification:
|
|
66
|
+
phone: str
|
|
67
|
+
|
|
68
|
+
def send(self, message: str) -> bool:
|
|
69
|
+
print(f"SMS to {self.phone}: {message}")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class SlackNotification:
|
|
74
|
+
channel: str
|
|
75
|
+
|
|
76
|
+
def send(self, message: str) -> bool:
|
|
77
|
+
print(f"Slack #{self.channel}: {message}")
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
# Python惯用:用字典注册表替代工厂子类层次
|
|
81
|
+
_notification_registry: dict[str, type] = {
|
|
82
|
+
"email": EmailNotification,
|
|
83
|
+
"sms": SMSNotification,
|
|
84
|
+
"slack": SlackNotification,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def create_notification(channel: str, **kwargs) -> Notification:
|
|
88
|
+
"""工厂函数:根据渠道类型创建通知对象"""
|
|
89
|
+
cls = _notification_registry.get(channel)
|
|
90
|
+
if cls is None:
|
|
91
|
+
raise ValueError(f"Unknown notification channel: {channel}")
|
|
92
|
+
return cls(**kwargs)
|
|
93
|
+
|
|
94
|
+
def register_notification(channel: str, cls: type) -> None:
|
|
95
|
+
"""运行时注册新的通知类型,支持插件式扩展"""
|
|
96
|
+
_notification_registry[channel] = cls
|
|
97
|
+
|
|
98
|
+
# 使用
|
|
99
|
+
notif = create_notification("email", recipient="user@example.com")
|
|
100
|
+
notif.send("Your order has shipped")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**使用场景**: 插件系统、多渠道通知、数据库驱动选择、序列化格式选择。
|
|
104
|
+
|
|
105
|
+
**注意事项**:
|
|
106
|
+
- 优先使用字典注册表而非类继承层次
|
|
107
|
+
- 工厂函数比工厂类更Pythonic
|
|
108
|
+
- 用`Protocol`定义产品接口而非抽象基类(除非需要默认实现)
|
|
109
|
+
|
|
110
|
+
### 2. 抽象工厂模式 (Abstract Factory)
|
|
111
|
+
|
|
112
|
+
**问题描述**: 需要创建一组相关或相互依赖的对象族,且不指定它们的具体类。
|
|
113
|
+
|
|
114
|
+
**Python实现**:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from __future__ import annotations
|
|
118
|
+
from typing import Protocol
|
|
119
|
+
from dataclasses import dataclass, field
|
|
120
|
+
|
|
121
|
+
class Button(Protocol):
|
|
122
|
+
def render(self) -> str: ...
|
|
123
|
+
|
|
124
|
+
class TextInput(Protocol):
|
|
125
|
+
def render(self) -> str: ...
|
|
126
|
+
|
|
127
|
+
class Theme(Protocol):
|
|
128
|
+
def create_button(self, label: str) -> Button: ...
|
|
129
|
+
def create_text_input(self, placeholder: str) -> TextInput: ...
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class MaterialButton:
|
|
133
|
+
label: str
|
|
134
|
+
def render(self) -> str:
|
|
135
|
+
return f'<button class="md-btn">{self.label}</button>'
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class MaterialTextInput:
|
|
139
|
+
placeholder: str
|
|
140
|
+
def render(self) -> str:
|
|
141
|
+
return f'<input class="md-input" placeholder="{self.placeholder}"/>'
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class AntDesignButton:
|
|
145
|
+
label: str
|
|
146
|
+
def render(self) -> str:
|
|
147
|
+
return f'<button class="ant-btn">{self.label}</button>'
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class AntDesignTextInput:
|
|
151
|
+
placeholder: str
|
|
152
|
+
def render(self) -> str:
|
|
153
|
+
return f'<input class="ant-input" placeholder="{self.placeholder}"/>'
|
|
154
|
+
|
|
155
|
+
class MaterialTheme:
|
|
156
|
+
def create_button(self, label: str) -> Button:
|
|
157
|
+
return MaterialButton(label)
|
|
158
|
+
def create_text_input(self, placeholder: str) -> TextInput:
|
|
159
|
+
return MaterialTextInput(placeholder)
|
|
160
|
+
|
|
161
|
+
class AntDesignTheme:
|
|
162
|
+
def create_button(self, label: str) -> Button:
|
|
163
|
+
return AntDesignButton(label)
|
|
164
|
+
def create_text_input(self, placeholder: str) -> TextInput:
|
|
165
|
+
return AntDesignTextInput(placeholder)
|
|
166
|
+
|
|
167
|
+
def build_form(theme: Theme) -> list[str]:
|
|
168
|
+
"""使用抽象工厂构建一致的UI表单"""
|
|
169
|
+
btn = theme.create_button("Submit")
|
|
170
|
+
inp = theme.create_text_input("Enter your name")
|
|
171
|
+
return [inp.render(), btn.render()]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**使用场景**: UI主题系统、跨数据库ORM适配、多云基础设施抽象。
|
|
175
|
+
|
|
176
|
+
**注意事项**:
|
|
177
|
+
- Python中抽象工厂的使用频率低于Java,因为鸭子类型天然支持多态
|
|
178
|
+
- 考虑是否真的需要对象族一致性,否则简单工厂函数即可
|
|
179
|
+
|
|
180
|
+
### 3. 建造者模式 (Builder)
|
|
181
|
+
|
|
182
|
+
**问题描述**: 构造复杂对象需要多个步骤,且不同表示可能需要相同的构造流程。
|
|
183
|
+
|
|
184
|
+
**Python实现**:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from __future__ import annotations
|
|
188
|
+
from dataclasses import dataclass, field
|
|
189
|
+
from typing import Self
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class HTTPRequest:
|
|
193
|
+
method: str = "GET"
|
|
194
|
+
url: str = ""
|
|
195
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
196
|
+
query_params: dict[str, str] = field(default_factory=dict)
|
|
197
|
+
body: str | bytes | None = None
|
|
198
|
+
timeout: float = 30.0
|
|
199
|
+
retries: int = 0
|
|
200
|
+
|
|
201
|
+
class Builder:
|
|
202
|
+
"""链式建造者,利用Python的Self类型实现流畅接口"""
|
|
203
|
+
def __init__(self) -> None:
|
|
204
|
+
self._method = "GET"
|
|
205
|
+
self._url = ""
|
|
206
|
+
self._headers: dict[str, str] = {}
|
|
207
|
+
self._query_params: dict[str, str] = {}
|
|
208
|
+
self._body: str | bytes | None = None
|
|
209
|
+
self._timeout: float = 30.0
|
|
210
|
+
self._retries: int = 0
|
|
211
|
+
|
|
212
|
+
def method(self, method: str) -> Self:
|
|
213
|
+
self._method = method
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def url(self, url: str) -> Self:
|
|
217
|
+
self._url = url
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def header(self, key: str, value: str) -> Self:
|
|
221
|
+
self._headers[key] = value
|
|
222
|
+
return self
|
|
223
|
+
|
|
224
|
+
def query(self, key: str, value: str) -> Self:
|
|
225
|
+
self._query_params[key] = value
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
def body(self, body: str | bytes) -> Self:
|
|
229
|
+
self._body = body
|
|
230
|
+
return self
|
|
231
|
+
|
|
232
|
+
def timeout(self, seconds: float) -> Self:
|
|
233
|
+
self._timeout = seconds
|
|
234
|
+
return self
|
|
235
|
+
|
|
236
|
+
def retries(self, count: int) -> Self:
|
|
237
|
+
self._retries = count
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def build(self) -> HTTPRequest:
|
|
241
|
+
if not self._url:
|
|
242
|
+
raise ValueError("URL is required")
|
|
243
|
+
return HTTPRequest(
|
|
244
|
+
method=self._method,
|
|
245
|
+
url=self._url,
|
|
246
|
+
headers=self._headers,
|
|
247
|
+
query_params=self._query_params,
|
|
248
|
+
body=self._body,
|
|
249
|
+
timeout=self._timeout,
|
|
250
|
+
retries=self._retries,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# 使用
|
|
254
|
+
request = (
|
|
255
|
+
HTTPRequest.Builder()
|
|
256
|
+
.method("POST")
|
|
257
|
+
.url("https://api.example.com/orders")
|
|
258
|
+
.header("Content-Type", "application/json")
|
|
259
|
+
.header("Authorization", "Bearer token123")
|
|
260
|
+
.body('{"item": "widget", "qty": 3}')
|
|
261
|
+
.timeout(10.0)
|
|
262
|
+
.retries(3)
|
|
263
|
+
.build()
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**使用场景**: 复杂配置对象、SQL/查询构建器、HTTP请求构建、测试数据工厂。
|
|
268
|
+
|
|
269
|
+
**注意事项**:
|
|
270
|
+
- Python更常使用`dataclass` + 关键字参数替代简单的Builder
|
|
271
|
+
- 仅当构造过程有验证逻辑或步骤顺序约束时才值得用Builder
|
|
272
|
+
- `Self`类型(Python 3.11+)使链式调用具有正确的类型推导
|
|
273
|
+
|
|
274
|
+
### 4. 单例模式 (Singleton)
|
|
275
|
+
|
|
276
|
+
**问题描述**: 确保一个类只有一个实例,并提供全局访问点。
|
|
277
|
+
|
|
278
|
+
**Python实现**:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
# 方式一:模块级变量(最Pythonic,推荐)
|
|
282
|
+
# config.py
|
|
283
|
+
class _AppConfig:
|
|
284
|
+
def __init__(self) -> None:
|
|
285
|
+
self._settings: dict[str, object] = {}
|
|
286
|
+
self._loaded = False
|
|
287
|
+
|
|
288
|
+
def load(self, path: str) -> None:
|
|
289
|
+
# 从文件加载配置
|
|
290
|
+
self._loaded = True
|
|
291
|
+
|
|
292
|
+
def get(self, key: str, default: object = None) -> object:
|
|
293
|
+
return self._settings.get(key, default)
|
|
294
|
+
|
|
295
|
+
# 模块级单例:Python模块天然只初始化一次
|
|
296
|
+
app_config = _AppConfig()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# 方式二:__new__控制实例化(需要类级别单例时)
|
|
300
|
+
class DatabasePool:
|
|
301
|
+
_instance: "DatabasePool | None" = None
|
|
302
|
+
|
|
303
|
+
def __new__(cls) -> "DatabasePool":
|
|
304
|
+
if cls._instance is None:
|
|
305
|
+
cls._instance = super().__new__(cls)
|
|
306
|
+
cls._instance._initialized = False
|
|
307
|
+
return cls._instance
|
|
308
|
+
|
|
309
|
+
def __init__(self) -> None:
|
|
310
|
+
if self._initialized:
|
|
311
|
+
return
|
|
312
|
+
self._initialized = True
|
|
313
|
+
self._connections: list = []
|
|
314
|
+
self._max_size = 10
|
|
315
|
+
|
|
316
|
+
def get_connection(self):
|
|
317
|
+
# 从池中获取连接
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# 方式三:元类(适用于框架级控制)
|
|
322
|
+
class SingletonMeta(type):
|
|
323
|
+
_instances: dict[type, object] = {}
|
|
324
|
+
|
|
325
|
+
def __call__(cls, *args, **kwargs):
|
|
326
|
+
if cls not in cls._instances:
|
|
327
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
328
|
+
return cls._instances[cls]
|
|
329
|
+
|
|
330
|
+
class CacheManager(metaclass=SingletonMeta):
|
|
331
|
+
def __init__(self) -> None:
|
|
332
|
+
self._store: dict[str, object] = {}
|
|
333
|
+
|
|
334
|
+
def get(self, key: str) -> object | None:
|
|
335
|
+
return self._store.get(key)
|
|
336
|
+
|
|
337
|
+
def set(self, key: str, value: object) -> None:
|
|
338
|
+
self._store[key] = value
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**使用场景**: 配置管理、连接池、缓存管理器、日志管理。
|
|
342
|
+
|
|
343
|
+
**注意事项**:
|
|
344
|
+
- **优先使用模块级变量**,这是最Pythonic的单例实现
|
|
345
|
+
- 单例模式使测试困难(全局状态),考虑依赖注入替代
|
|
346
|
+
- 线程安全场景需加锁或使用`threading.Lock`
|
|
347
|
+
- 避免过度使用——大多数"需要单例"的场景实际上可以通过依赖注入解决
|
|
348
|
+
|
|
349
|
+
### 5. 原型模式 (Prototype)
|
|
350
|
+
|
|
351
|
+
**问题描述**: 通过复制现有对象来创建新对象,避免重复的初始化开销。
|
|
352
|
+
|
|
353
|
+
**Python实现**:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
import copy
|
|
357
|
+
from dataclasses import dataclass, field
|
|
358
|
+
|
|
359
|
+
@dataclass
|
|
360
|
+
class GameCharacter:
|
|
361
|
+
name: str
|
|
362
|
+
health: int
|
|
363
|
+
attack: int
|
|
364
|
+
defense: int
|
|
365
|
+
skills: list[str] = field(default_factory=list)
|
|
366
|
+
inventory: dict[str, int] = field(default_factory=dict)
|
|
367
|
+
|
|
368
|
+
def clone(self, **overrides) -> "GameCharacter":
|
|
369
|
+
"""深拷贝并允许覆盖特定字段"""
|
|
370
|
+
cloned = copy.deepcopy(self)
|
|
371
|
+
for key, value in overrides.items():
|
|
372
|
+
if not hasattr(cloned, key):
|
|
373
|
+
raise AttributeError(f"GameCharacter has no attribute '{key}'")
|
|
374
|
+
setattr(cloned, key, value)
|
|
375
|
+
return cloned
|
|
376
|
+
|
|
377
|
+
# 预设原型注册表
|
|
378
|
+
_character_prototypes: dict[str, GameCharacter] = {
|
|
379
|
+
"warrior": GameCharacter(
|
|
380
|
+
name="Warrior",
|
|
381
|
+
health=150,
|
|
382
|
+
attack=30,
|
|
383
|
+
defense=25,
|
|
384
|
+
skills=["slash", "shield_bash"],
|
|
385
|
+
inventory={"health_potion": 3},
|
|
386
|
+
),
|
|
387
|
+
"mage": GameCharacter(
|
|
388
|
+
name="Mage",
|
|
389
|
+
health=80,
|
|
390
|
+
attack=50,
|
|
391
|
+
defense=10,
|
|
392
|
+
skills=["fireball", "ice_shield"],
|
|
393
|
+
inventory={"mana_potion": 5},
|
|
394
|
+
),
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
def create_character(archetype: str, name: str) -> GameCharacter:
|
|
398
|
+
proto = _character_prototypes.get(archetype)
|
|
399
|
+
if proto is None:
|
|
400
|
+
raise ValueError(f"Unknown archetype: {archetype}")
|
|
401
|
+
return proto.clone(name=name)
|
|
402
|
+
|
|
403
|
+
# 使用
|
|
404
|
+
hero = create_character("warrior", name="Arthas")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**使用场景**: 配置模板、游戏对象预设、测试数据快速构造、文档模板。
|
|
408
|
+
|
|
409
|
+
**注意事项**:
|
|
410
|
+
- Python的`copy.deepcopy`是原型模式的天然支持
|
|
411
|
+
- 注意深拷贝与浅拷贝的区别,可变嵌套对象必须深拷贝
|
|
412
|
+
- `dataclass`的`replace`(Python 3.13+)提供了更安全的浅拷贝
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 结构型模式
|
|
417
|
+
|
|
418
|
+
### 1. 适配器模式 (Adapter)
|
|
419
|
+
|
|
420
|
+
**问题描述**: 将一个接口转换为客户端期望的另一种接口,使不兼容的类可以协作。
|
|
421
|
+
|
|
422
|
+
**Python实现**:
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
from typing import Protocol
|
|
426
|
+
import json
|
|
427
|
+
import xml.etree.ElementTree as ET
|
|
428
|
+
|
|
429
|
+
class DataParser(Protocol):
|
|
430
|
+
def parse(self, raw: str) -> dict: ...
|
|
431
|
+
|
|
432
|
+
class JSONParser:
|
|
433
|
+
"""原生支持的JSON解析器"""
|
|
434
|
+
def parse(self, raw: str) -> dict:
|
|
435
|
+
return json.loads(raw)
|
|
436
|
+
|
|
437
|
+
class LegacyXMLService:
|
|
438
|
+
"""遗留系统,接口不符合DataParser协议"""
|
|
439
|
+
def read_xml(self, xml_string: str) -> ET.Element:
|
|
440
|
+
return ET.fromstring(xml_string)
|
|
441
|
+
|
|
442
|
+
def extract_data(self, element: ET.Element) -> dict[str, str]:
|
|
443
|
+
return {child.tag: (child.text or "") for child in element}
|
|
444
|
+
|
|
445
|
+
class XMLParserAdapter:
|
|
446
|
+
"""适配器:将LegacyXMLService适配为DataParser接口"""
|
|
447
|
+
def __init__(self, legacy_service: LegacyXMLService | None = None) -> None:
|
|
448
|
+
self._service = legacy_service or LegacyXMLService()
|
|
449
|
+
|
|
450
|
+
def parse(self, raw: str) -> dict:
|
|
451
|
+
element = self._service.read_xml(raw)
|
|
452
|
+
return self._service.extract_data(element)
|
|
453
|
+
|
|
454
|
+
def process_data(parser: DataParser, raw: str) -> dict:
|
|
455
|
+
"""客户端代码只依赖DataParser协议"""
|
|
456
|
+
return parser.parse(raw)
|
|
457
|
+
|
|
458
|
+
# 使用
|
|
459
|
+
json_result = process_data(JSONParser(), '{"name": "Alice"}')
|
|
460
|
+
xml_result = process_data(XMLParserAdapter(), "<root><name>Alice</name></root>")
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**使用场景**: 第三方库集成、遗留系统封装、API版本适配、多数据源统一接口。
|
|
464
|
+
|
|
465
|
+
**注意事项**:
|
|
466
|
+
- Python的鸭子类型使适配更轻量——只需实现相同的方法签名
|
|
467
|
+
- 优先使用组合(持有被适配对象引用)而非继承
|
|
468
|
+
|
|
469
|
+
### 2. 装饰器模式 (Decorator)
|
|
470
|
+
|
|
471
|
+
**问题描述**: 动态地给对象添加额外职责,比子类化更灵活。
|
|
472
|
+
|
|
473
|
+
**Python实现**:
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
import functools
|
|
477
|
+
import time
|
|
478
|
+
import logging
|
|
479
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
480
|
+
|
|
481
|
+
P = ParamSpec("P")
|
|
482
|
+
R = TypeVar("R")
|
|
483
|
+
|
|
484
|
+
logger = logging.getLogger(__name__)
|
|
485
|
+
|
|
486
|
+
def retry(max_attempts: int = 3, delay: float = 1.0):
|
|
487
|
+
"""重试装饰器:失败时自动重试"""
|
|
488
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
489
|
+
@functools.wraps(func)
|
|
490
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
491
|
+
last_exception: Exception | None = None
|
|
492
|
+
for attempt in range(1, max_attempts + 1):
|
|
493
|
+
try:
|
|
494
|
+
return func(*args, **kwargs)
|
|
495
|
+
except Exception as exc:
|
|
496
|
+
last_exception = exc
|
|
497
|
+
logger.warning(
|
|
498
|
+
"Attempt %d/%d failed for %s: %s",
|
|
499
|
+
attempt, max_attempts, func.__name__, exc,
|
|
500
|
+
)
|
|
501
|
+
if attempt < max_attempts:
|
|
502
|
+
time.sleep(delay * attempt) # 指数退避
|
|
503
|
+
raise last_exception # type: ignore[misc]
|
|
504
|
+
return wrapper
|
|
505
|
+
return decorator
|
|
506
|
+
|
|
507
|
+
def timing(func: Callable[P, R]) -> Callable[P, R]:
|
|
508
|
+
"""计时装饰器:记录函数执行时间"""
|
|
509
|
+
@functools.wraps(func)
|
|
510
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
511
|
+
start = time.perf_counter()
|
|
512
|
+
try:
|
|
513
|
+
result = func(*args, **kwargs)
|
|
514
|
+
return result
|
|
515
|
+
finally:
|
|
516
|
+
elapsed = time.perf_counter() - start
|
|
517
|
+
logger.info("%s took %.3fs", func.__name__, elapsed)
|
|
518
|
+
return wrapper
|
|
519
|
+
|
|
520
|
+
def cache_result(ttl_seconds: float = 60.0):
|
|
521
|
+
"""带TTL的缓存装饰器"""
|
|
522
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
523
|
+
_cache: dict[tuple, tuple[float, R]] = {}
|
|
524
|
+
|
|
525
|
+
@functools.wraps(func)
|
|
526
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
527
|
+
key = (args, tuple(sorted(kwargs.items())))
|
|
528
|
+
now = time.time()
|
|
529
|
+
if key in _cache:
|
|
530
|
+
cached_time, cached_value = _cache[key]
|
|
531
|
+
if now - cached_time < ttl_seconds:
|
|
532
|
+
return cached_value
|
|
533
|
+
result = func(*args, **kwargs)
|
|
534
|
+
_cache[key] = (now, result)
|
|
535
|
+
return result
|
|
536
|
+
return wrapper
|
|
537
|
+
return decorator
|
|
538
|
+
|
|
539
|
+
# 装饰器堆叠:从下往上应用
|
|
540
|
+
@timing
|
|
541
|
+
@retry(max_attempts=3, delay=0.5)
|
|
542
|
+
def fetch_user_data(user_id: int) -> dict:
|
|
543
|
+
"""模拟获取用户数据的远程调用"""
|
|
544
|
+
# 实际实现...
|
|
545
|
+
return {"id": user_id, "name": "Alice"}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**使用场景**: 日志、缓存、认证检查、重试、限流、参数校验、事务管理。
|
|
549
|
+
|
|
550
|
+
**注意事项**:
|
|
551
|
+
- **必须使用`@functools.wraps`**保留原函数元信息(`__name__`, `__doc__`, `__module__`)
|
|
552
|
+
- 装饰器堆叠顺序很重要,从下到上执行(最内层先执行)
|
|
553
|
+
- 用`ParamSpec`和`TypeVar`保持正确的类型签名
|
|
554
|
+
- 对类方法使用装饰器时注意`self`参数的处理
|
|
555
|
+
|
|
556
|
+
### 3. 代理模式 (Proxy)
|
|
557
|
+
|
|
558
|
+
**问题描述**: 控制对另一个对象的访问,提供额外的间接层。
|
|
559
|
+
|
|
560
|
+
**Python实现**:
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
from typing import Protocol
|
|
564
|
+
import time
|
|
565
|
+
|
|
566
|
+
class ImageLoader(Protocol):
|
|
567
|
+
def load(self) -> bytes: ...
|
|
568
|
+
@property
|
|
569
|
+
def filename(self) -> str: ...
|
|
570
|
+
|
|
571
|
+
class RealImageLoader:
|
|
572
|
+
"""真实的图片加载器,可能涉及昂贵的IO操作"""
|
|
573
|
+
def __init__(self, filename: str) -> None:
|
|
574
|
+
self._filename = filename
|
|
575
|
+
self._data: bytes | None = None
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def filename(self) -> str:
|
|
579
|
+
return self._filename
|
|
580
|
+
|
|
581
|
+
def load(self) -> bytes:
|
|
582
|
+
if self._data is None:
|
|
583
|
+
print(f"Loading image from disk: {self._filename}")
|
|
584
|
+
time.sleep(0.5) # 模拟IO延迟
|
|
585
|
+
self._data = b"<image-binary-data>"
|
|
586
|
+
return self._data
|
|
587
|
+
|
|
588
|
+
class LazyImageProxy:
|
|
589
|
+
"""延迟加载代理:直到真正需要数据时才加载"""
|
|
590
|
+
def __init__(self, filename: str) -> None:
|
|
591
|
+
self._filename = filename
|
|
592
|
+
self._real_loader: RealImageLoader | None = None
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def filename(self) -> str:
|
|
596
|
+
return self._filename
|
|
597
|
+
|
|
598
|
+
def _ensure_loaded(self) -> RealImageLoader:
|
|
599
|
+
if self._real_loader is None:
|
|
600
|
+
self._real_loader = RealImageLoader(self._filename)
|
|
601
|
+
return self._real_loader
|
|
602
|
+
|
|
603
|
+
def load(self) -> bytes:
|
|
604
|
+
return self._ensure_loaded().load()
|
|
605
|
+
|
|
606
|
+
class AccessControlProxy:
|
|
607
|
+
"""访问控制代理:检查权限后转发请求"""
|
|
608
|
+
def __init__(self, loader: ImageLoader, allowed_roles: set[str]) -> None:
|
|
609
|
+
self._loader = loader
|
|
610
|
+
self._allowed_roles = allowed_roles
|
|
611
|
+
|
|
612
|
+
@property
|
|
613
|
+
def filename(self) -> str:
|
|
614
|
+
return self._loader.filename
|
|
615
|
+
|
|
616
|
+
def load(self, *, role: str = "viewer") -> bytes:
|
|
617
|
+
if role not in self._allowed_roles:
|
|
618
|
+
raise PermissionError(
|
|
619
|
+
f"Role '{role}' not allowed to load {self.filename}"
|
|
620
|
+
)
|
|
621
|
+
return self._loader.load()
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**使用场景**: 延迟加载、访问控制、缓存代理、远程代理(RPC)、日志代理。
|
|
625
|
+
|
|
626
|
+
**注意事项**:
|
|
627
|
+
- Python中`__getattr__`可以实现透明代理,自动转发所有未定义的属性访问
|
|
628
|
+
- 与装饰器模式的区别:代理控制访问,装饰器增强功能
|
|
629
|
+
|
|
630
|
+
### 4. 外观模式 (Facade)
|
|
631
|
+
|
|
632
|
+
**问题描述**: 为复杂子系统提供简化的统一接口。
|
|
633
|
+
|
|
634
|
+
**Python实现**:
|
|
635
|
+
|
|
636
|
+
```python
|
|
637
|
+
from dataclasses import dataclass
|
|
638
|
+
|
|
639
|
+
# 复杂的子系统组件
|
|
640
|
+
class InventoryService:
|
|
641
|
+
def check_stock(self, product_id: str) -> int:
|
|
642
|
+
return 42 # 模拟库存查询
|
|
643
|
+
|
|
644
|
+
def reserve(self, product_id: str, quantity: int) -> str:
|
|
645
|
+
return f"RESERVE-{product_id}-{quantity}"
|
|
646
|
+
|
|
647
|
+
class PaymentService:
|
|
648
|
+
def charge(self, amount: float, payment_method: str) -> str:
|
|
649
|
+
return f"TXN-{amount}"
|
|
650
|
+
|
|
651
|
+
def refund(self, transaction_id: str) -> bool:
|
|
652
|
+
return True
|
|
653
|
+
|
|
654
|
+
class ShippingService:
|
|
655
|
+
def calculate_cost(self, weight: float, destination: str) -> float:
|
|
656
|
+
return weight * 2.5
|
|
657
|
+
|
|
658
|
+
def create_shipment(self, order_id: str, destination: str) -> str:
|
|
659
|
+
return f"SHIP-{order_id}"
|
|
660
|
+
|
|
661
|
+
class NotificationService:
|
|
662
|
+
def send_email(self, to: str, subject: str, body: str) -> None:
|
|
663
|
+
pass
|
|
664
|
+
|
|
665
|
+
def send_sms(self, phone: str, message: str) -> None:
|
|
666
|
+
pass
|
|
667
|
+
|
|
668
|
+
@dataclass
|
|
669
|
+
class OrderResult:
|
|
670
|
+
order_id: str
|
|
671
|
+
transaction_id: str
|
|
672
|
+
shipment_id: str
|
|
673
|
+
total_cost: float
|
|
674
|
+
|
|
675
|
+
class OrderFacade:
|
|
676
|
+
"""订单外观:将复杂的多步骤订单流程封装为简单接口"""
|
|
677
|
+
def __init__(
|
|
678
|
+
self,
|
|
679
|
+
inventory: InventoryService | None = None,
|
|
680
|
+
payment: PaymentService | None = None,
|
|
681
|
+
shipping: ShippingService | None = None,
|
|
682
|
+
notification: NotificationService | None = None,
|
|
683
|
+
) -> None:
|
|
684
|
+
self._inventory = inventory or InventoryService()
|
|
685
|
+
self._payment = payment or PaymentService()
|
|
686
|
+
self._shipping = shipping or ShippingService()
|
|
687
|
+
self._notification = notification or NotificationService()
|
|
688
|
+
|
|
689
|
+
def place_order(
|
|
690
|
+
self,
|
|
691
|
+
product_id: str,
|
|
692
|
+
quantity: int,
|
|
693
|
+
payment_method: str,
|
|
694
|
+
destination: str,
|
|
695
|
+
customer_email: str,
|
|
696
|
+
) -> OrderResult:
|
|
697
|
+
"""一步完成下单:检查库存->预留->计费->发货->通知"""
|
|
698
|
+
stock = self._inventory.check_stock(product_id)
|
|
699
|
+
if stock < quantity:
|
|
700
|
+
raise ValueError(f"Insufficient stock: {stock} < {quantity}")
|
|
701
|
+
|
|
702
|
+
reservation = self._inventory.reserve(product_id, quantity)
|
|
703
|
+
shipping_cost = self._shipping.calculate_cost(quantity * 0.5, destination)
|
|
704
|
+
total = quantity * 19.99 + shipping_cost
|
|
705
|
+
txn_id = self._payment.charge(total, payment_method)
|
|
706
|
+
ship_id = self._shipping.create_shipment(reservation, destination)
|
|
707
|
+
self._notification.send_email(
|
|
708
|
+
customer_email, "Order Confirmed", f"Shipment: {ship_id}"
|
|
709
|
+
)
|
|
710
|
+
return OrderResult(
|
|
711
|
+
order_id=reservation,
|
|
712
|
+
transaction_id=txn_id,
|
|
713
|
+
shipment_id=ship_id,
|
|
714
|
+
total_cost=total,
|
|
715
|
+
)
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**使用场景**: 复杂API简化、子系统封装、微服务聚合层、SDK封装。
|
|
719
|
+
|
|
720
|
+
**注意事项**:
|
|
721
|
+
- 外观不应成为"上帝对象"——只封装常见工作流,不代替子系统
|
|
722
|
+
- 允许客户端在需要时绕过外观直接使用子系统
|
|
723
|
+
|
|
724
|
+
### 5. 组合模式 (Composite)
|
|
725
|
+
|
|
726
|
+
**问题描述**: 将对象组合成树形结构以表示"部分-整体"层次,使客户端对单个对象和组合对象的使用具有一致性。
|
|
727
|
+
|
|
728
|
+
**Python实现**:
|
|
729
|
+
|
|
730
|
+
```python
|
|
731
|
+
from __future__ import annotations
|
|
732
|
+
from dataclasses import dataclass, field
|
|
733
|
+
|
|
734
|
+
@dataclass
|
|
735
|
+
class FileSystemItem:
|
|
736
|
+
name: str
|
|
737
|
+
|
|
738
|
+
def size(self) -> int:
|
|
739
|
+
raise NotImplementedError
|
|
740
|
+
|
|
741
|
+
def display(self, indent: int = 0) -> str:
|
|
742
|
+
raise NotImplementedError
|
|
743
|
+
|
|
744
|
+
@dataclass
|
|
745
|
+
class File(FileSystemItem):
|
|
746
|
+
_size: int = 0
|
|
747
|
+
|
|
748
|
+
def size(self) -> int:
|
|
749
|
+
return self._size
|
|
750
|
+
|
|
751
|
+
def display(self, indent: int = 0) -> str:
|
|
752
|
+
prefix = " " * indent
|
|
753
|
+
return f"{prefix}{self.name} ({self._size}B)"
|
|
754
|
+
|
|
755
|
+
@dataclass
|
|
756
|
+
class Directory(FileSystemItem):
|
|
757
|
+
children: list[FileSystemItem] = field(default_factory=list)
|
|
758
|
+
|
|
759
|
+
def add(self, item: FileSystemItem) -> None:
|
|
760
|
+
self.children.append(item)
|
|
761
|
+
|
|
762
|
+
def remove(self, name: str) -> None:
|
|
763
|
+
self.children = [c for c in self.children if c.name != name]
|
|
764
|
+
|
|
765
|
+
def size(self) -> int:
|
|
766
|
+
return sum(child.size() for child in self.children)
|
|
767
|
+
|
|
768
|
+
def display(self, indent: int = 0) -> str:
|
|
769
|
+
prefix = " " * indent
|
|
770
|
+
lines = [f"{prefix}{self.name}/ ({self.size()}B)"]
|
|
771
|
+
for child in self.children:
|
|
772
|
+
lines.append(child.display(indent + 1))
|
|
773
|
+
return "\n".join(lines)
|
|
774
|
+
|
|
775
|
+
# 使用
|
|
776
|
+
root = Directory("project")
|
|
777
|
+
src = Directory("src")
|
|
778
|
+
src.add(File("main.py", 1200))
|
|
779
|
+
src.add(File("utils.py", 800))
|
|
780
|
+
root.add(src)
|
|
781
|
+
root.add(File("README.md", 500))
|
|
782
|
+
print(root.display())
|
|
783
|
+
# project/ (2500B)
|
|
784
|
+
# src/ (2000B)
|
|
785
|
+
# main.py (1200B)
|
|
786
|
+
# utils.py (800B)
|
|
787
|
+
# README.md (500B)
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**使用场景**: 文件系统、UI组件树、组织架构、菜单系统、权限树。
|
|
791
|
+
|
|
792
|
+
**注意事项**:
|
|
793
|
+
- 注意避免循环引用(目录不应包含自己的祖先)
|
|
794
|
+
- 递归操作注意深度限制(`sys.setrecursionlimit`)
|
|
795
|
+
|
|
796
|
+
### 6. 享元模式 (Flyweight)
|
|
797
|
+
|
|
798
|
+
**问题描述**: 通过共享细粒度对象来减少内存使用。
|
|
799
|
+
|
|
800
|
+
**Python实现**:
|
|
801
|
+
|
|
802
|
+
```python
|
|
803
|
+
import weakref
|
|
804
|
+
from dataclasses import dataclass
|
|
805
|
+
|
|
806
|
+
@dataclass(frozen=True)
|
|
807
|
+
class TextStyle:
|
|
808
|
+
"""不可变的文本样式——内在状态,可共享"""
|
|
809
|
+
font_family: str
|
|
810
|
+
font_size: int
|
|
811
|
+
bold: bool
|
|
812
|
+
italic: bool
|
|
813
|
+
color: str
|
|
814
|
+
|
|
815
|
+
class TextStyleFactory:
|
|
816
|
+
"""享元工厂:相同样式只创建一个实例"""
|
|
817
|
+
_cache: dict[tuple, TextStyle] = {}
|
|
818
|
+
|
|
819
|
+
@classmethod
|
|
820
|
+
def get_style(
|
|
821
|
+
cls,
|
|
822
|
+
font_family: str = "Arial",
|
|
823
|
+
font_size: int = 12,
|
|
824
|
+
bold: bool = False,
|
|
825
|
+
italic: bool = False,
|
|
826
|
+
color: str = "#000000",
|
|
827
|
+
) -> TextStyle:
|
|
828
|
+
key = (font_family, font_size, bold, italic, color)
|
|
829
|
+
if key not in cls._cache:
|
|
830
|
+
cls._cache[key] = TextStyle(
|
|
831
|
+
font_family=font_family,
|
|
832
|
+
font_size=font_size,
|
|
833
|
+
bold=bold,
|
|
834
|
+
italic=italic,
|
|
835
|
+
color=color,
|
|
836
|
+
)
|
|
837
|
+
return cls._cache[key]
|
|
838
|
+
|
|
839
|
+
@classmethod
|
|
840
|
+
def cache_size(cls) -> int:
|
|
841
|
+
return len(cls._cache)
|
|
842
|
+
|
|
843
|
+
@dataclass
|
|
844
|
+
class TextCharacter:
|
|
845
|
+
"""单个字符——外在状态(位置)+ 共享的内在状态(样式)"""
|
|
846
|
+
char: str
|
|
847
|
+
x: int
|
|
848
|
+
y: int
|
|
849
|
+
style: TextStyle
|
|
850
|
+
|
|
851
|
+
# 使用:100万个字符只需少量样式对象
|
|
852
|
+
body_style = TextStyleFactory.get_style("Arial", 12, False, False, "#333")
|
|
853
|
+
bold_style = TextStyleFactory.get_style("Arial", 12, True, False, "#333")
|
|
854
|
+
characters = [TextCharacter("H", 0, 0, bold_style)]
|
|
855
|
+
characters.extend(TextCharacter(c, i * 8, 0, body_style) for i, c in enumerate("ello World", 1))
|
|
856
|
+
# 尽管有11个字符对象,只创建了2个样式对象
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
**使用场景**: 文本渲染、地图瓦片、粒子系统、字符串驻留。
|
|
860
|
+
|
|
861
|
+
**注意事项**:
|
|
862
|
+
- Python的`str`和小整数已使用享元(字符串驻留、整数缓存)
|
|
863
|
+
- 用`frozen=True`的`dataclass`或`__slots__`进一步优化
|
|
864
|
+
- `weakref.WeakValueDictionary`可让不再引用的享元被GC回收
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## 行为型模式
|
|
869
|
+
|
|
870
|
+
### 1. 策略模式 (Strategy)
|
|
871
|
+
|
|
872
|
+
**问题描述**: 定义一族算法,将每个算法封装起来,使它们可以互相替换。
|
|
873
|
+
|
|
874
|
+
**Python实现**:
|
|
875
|
+
|
|
876
|
+
```python
|
|
877
|
+
from typing import Callable
|
|
878
|
+
from dataclasses import dataclass
|
|
879
|
+
|
|
880
|
+
# Python惯用:策略就是函数
|
|
881
|
+
PricingStrategy = Callable[[float, int], float]
|
|
882
|
+
|
|
883
|
+
def regular_pricing(unit_price: float, quantity: int) -> float:
|
|
884
|
+
"""标准定价"""
|
|
885
|
+
return unit_price * quantity
|
|
886
|
+
|
|
887
|
+
def bulk_pricing(unit_price: float, quantity: int) -> float:
|
|
888
|
+
"""批量折扣:10件以上打8折"""
|
|
889
|
+
total = unit_price * quantity
|
|
890
|
+
if quantity > 10:
|
|
891
|
+
total *= 0.8
|
|
892
|
+
return total
|
|
893
|
+
|
|
894
|
+
def vip_pricing(unit_price: float, quantity: int) -> float:
|
|
895
|
+
"""VIP定价:全场7折"""
|
|
896
|
+
return unit_price * quantity * 0.7
|
|
897
|
+
|
|
898
|
+
def seasonal_pricing(discount: float = 0.85) -> PricingStrategy:
|
|
899
|
+
"""季节性定价:闭包捕获折扣率"""
|
|
900
|
+
def strategy(unit_price: float, quantity: int) -> float:
|
|
901
|
+
return unit_price * quantity * discount
|
|
902
|
+
return strategy
|
|
903
|
+
|
|
904
|
+
@dataclass
|
|
905
|
+
class Order:
|
|
906
|
+
product: str
|
|
907
|
+
unit_price: float
|
|
908
|
+
quantity: int
|
|
909
|
+
pricing: PricingStrategy = regular_pricing # 默认策略
|
|
910
|
+
|
|
911
|
+
def total(self) -> float:
|
|
912
|
+
return self.pricing(self.unit_price, self.quantity)
|
|
913
|
+
|
|
914
|
+
# 使用:策略作为参数传入
|
|
915
|
+
order_a = Order("Widget", 10.0, 5, pricing=regular_pricing)
|
|
916
|
+
order_b = Order("Widget", 10.0, 20, pricing=bulk_pricing)
|
|
917
|
+
order_c = Order("Widget", 10.0, 5, pricing=seasonal_pricing(0.9))
|
|
918
|
+
print(order_a.total()) # 50.0
|
|
919
|
+
print(order_b.total()) # 160.0
|
|
920
|
+
print(order_c.total()) # 45.0
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
**使用场景**: 定价策略、排序算法选择、验证规则、序列化格式、压缩算法。
|
|
924
|
+
|
|
925
|
+
**注意事项**:
|
|
926
|
+
- Python中策略模式的最佳实现就是传递函数,无需类层次
|
|
927
|
+
- 需要携带状态的策略可用闭包或callable类
|
|
928
|
+
- 避免为了"模式"而创建不必要的Strategy接口和实现类
|
|
929
|
+
|
|
930
|
+
### 2. 观察者模式 (Observer)
|
|
931
|
+
|
|
932
|
+
**问题描述**: 定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖者都得到通知。
|
|
933
|
+
|
|
934
|
+
**Python实现**:
|
|
935
|
+
|
|
936
|
+
```python
|
|
937
|
+
from __future__ import annotations
|
|
938
|
+
from typing import Callable, Any
|
|
939
|
+
from dataclasses import dataclass, field
|
|
940
|
+
from collections import defaultdict
|
|
941
|
+
import weakref
|
|
942
|
+
|
|
943
|
+
# 事件类型标识
|
|
944
|
+
type EventHandler = Callable[[str, dict[str, Any]], None]
|
|
945
|
+
|
|
946
|
+
class EventBus:
|
|
947
|
+
"""轻量级事件总线:基于回调函数的观察者模式"""
|
|
948
|
+
def __init__(self) -> None:
|
|
949
|
+
self._handlers: dict[str, list[EventHandler]] = defaultdict(list)
|
|
950
|
+
|
|
951
|
+
def subscribe(self, event: str, handler: EventHandler) -> Callable[[], None]:
|
|
952
|
+
"""订阅事件,返回取消订阅的函数"""
|
|
953
|
+
self._handlers[event].append(handler)
|
|
954
|
+
def unsubscribe() -> None:
|
|
955
|
+
self._handlers[event].remove(handler)
|
|
956
|
+
return unsubscribe
|
|
957
|
+
|
|
958
|
+
def publish(self, event: str, data: dict[str, Any] | None = None) -> None:
|
|
959
|
+
"""发布事件,通知所有订阅者"""
|
|
960
|
+
for handler in self._handlers.get(event, []):
|
|
961
|
+
handler(event, data or {})
|
|
962
|
+
|
|
963
|
+
def clear(self, event: str | None = None) -> None:
|
|
964
|
+
if event:
|
|
965
|
+
self._handlers.pop(event, None)
|
|
966
|
+
else:
|
|
967
|
+
self._handlers.clear()
|
|
968
|
+
|
|
969
|
+
# 使用
|
|
970
|
+
bus = EventBus()
|
|
971
|
+
|
|
972
|
+
def on_user_created(event: str, data: dict[str, Any]) -> None:
|
|
973
|
+
print(f"Send welcome email to {data['email']}")
|
|
974
|
+
|
|
975
|
+
def on_user_created_audit(event: str, data: dict[str, Any]) -> None:
|
|
976
|
+
print(f"Audit log: user {data['user_id']} created")
|
|
977
|
+
|
|
978
|
+
unsub1 = bus.subscribe("user.created", on_user_created)
|
|
979
|
+
unsub2 = bus.subscribe("user.created", on_user_created_audit)
|
|
980
|
+
|
|
981
|
+
bus.publish("user.created", {"user_id": 42, "email": "new@example.com"})
|
|
982
|
+
# Send welcome email to new@example.com
|
|
983
|
+
# Audit log: user 42 created
|
|
984
|
+
|
|
985
|
+
unsub1() # 取消welcome邮件的订阅
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**使用场景**: UI事件处理、领域事件、消息队列抽象、插件系统、数据绑定。
|
|
989
|
+
|
|
990
|
+
**注意事项**:
|
|
991
|
+
- 注意内存泄漏:长生命周期的事件总线可能持有已不需要的handler引用
|
|
992
|
+
- 考虑使用`weakref`避免循环引用
|
|
993
|
+
- 异步场景使用`asyncio.Event`或异步事件总线
|
|
994
|
+
|
|
995
|
+
### 3. 命令模式 (Command)
|
|
996
|
+
|
|
997
|
+
**问题描述**: 将请求封装为对象,支持撤销、队列和日志记录。
|
|
998
|
+
|
|
999
|
+
**Python实现**:
|
|
1000
|
+
|
|
1001
|
+
```python
|
|
1002
|
+
from __future__ import annotations
|
|
1003
|
+
from typing import Protocol
|
|
1004
|
+
from dataclasses import dataclass, field
|
|
1005
|
+
|
|
1006
|
+
class Command(Protocol):
|
|
1007
|
+
def execute(self) -> None: ...
|
|
1008
|
+
def undo(self) -> None: ...
|
|
1009
|
+
@property
|
|
1010
|
+
def description(self) -> str: ...
|
|
1011
|
+
|
|
1012
|
+
@dataclass
|
|
1013
|
+
class TextDocument:
|
|
1014
|
+
content: str = ""
|
|
1015
|
+
|
|
1016
|
+
def insert(self, position: int, text: str) -> None:
|
|
1017
|
+
self.content = self.content[:position] + text + self.content[position:]
|
|
1018
|
+
|
|
1019
|
+
def delete(self, position: int, length: int) -> str:
|
|
1020
|
+
deleted = self.content[position:position + length]
|
|
1021
|
+
self.content = self.content[:position] + self.content[position + length:]
|
|
1022
|
+
return deleted
|
|
1023
|
+
|
|
1024
|
+
@dataclass
|
|
1025
|
+
class InsertCommand:
|
|
1026
|
+
doc: TextDocument
|
|
1027
|
+
position: int
|
|
1028
|
+
text: str
|
|
1029
|
+
|
|
1030
|
+
@property
|
|
1031
|
+
def description(self) -> str:
|
|
1032
|
+
return f"Insert '{self.text}' at {self.position}"
|
|
1033
|
+
|
|
1034
|
+
def execute(self) -> None:
|
|
1035
|
+
self.doc.insert(self.position, self.text)
|
|
1036
|
+
|
|
1037
|
+
def undo(self) -> None:
|
|
1038
|
+
self.doc.delete(self.position, len(self.text))
|
|
1039
|
+
|
|
1040
|
+
@dataclass
|
|
1041
|
+
class DeleteCommand:
|
|
1042
|
+
doc: TextDocument
|
|
1043
|
+
position: int
|
|
1044
|
+
length: int
|
|
1045
|
+
_deleted_text: str = field(default="", init=False)
|
|
1046
|
+
|
|
1047
|
+
@property
|
|
1048
|
+
def description(self) -> str:
|
|
1049
|
+
return f"Delete {self.length} chars at {self.position}"
|
|
1050
|
+
|
|
1051
|
+
def execute(self) -> None:
|
|
1052
|
+
self._deleted_text = self.doc.delete(self.position, self.length)
|
|
1053
|
+
|
|
1054
|
+
def undo(self) -> None:
|
|
1055
|
+
self.doc.insert(self.position, self._deleted_text)
|
|
1056
|
+
|
|
1057
|
+
class CommandHistory:
|
|
1058
|
+
"""命令历史管理器:支持撤销/重做"""
|
|
1059
|
+
def __init__(self) -> None:
|
|
1060
|
+
self._undo_stack: list[Command] = []
|
|
1061
|
+
self._redo_stack: list[Command] = []
|
|
1062
|
+
|
|
1063
|
+
def execute(self, command: Command) -> None:
|
|
1064
|
+
command.execute()
|
|
1065
|
+
self._undo_stack.append(command)
|
|
1066
|
+
self._redo_stack.clear() # 新命令清除重做栈
|
|
1067
|
+
|
|
1068
|
+
def undo(self) -> None:
|
|
1069
|
+
if not self._undo_stack:
|
|
1070
|
+
return
|
|
1071
|
+
cmd = self._undo_stack.pop()
|
|
1072
|
+
cmd.undo()
|
|
1073
|
+
self._redo_stack.append(cmd)
|
|
1074
|
+
|
|
1075
|
+
def redo(self) -> None:
|
|
1076
|
+
if not self._redo_stack:
|
|
1077
|
+
return
|
|
1078
|
+
cmd = self._redo_stack.pop()
|
|
1079
|
+
cmd.execute()
|
|
1080
|
+
self._undo_stack.append(cmd)
|
|
1081
|
+
|
|
1082
|
+
# 使用
|
|
1083
|
+
doc = TextDocument()
|
|
1084
|
+
history = CommandHistory()
|
|
1085
|
+
history.execute(InsertCommand(doc, 0, "Hello World"))
|
|
1086
|
+
history.execute(InsertCommand(doc, 5, ","))
|
|
1087
|
+
print(doc.content) # "Hello, World"
|
|
1088
|
+
history.undo()
|
|
1089
|
+
print(doc.content) # "Hello World"
|
|
1090
|
+
history.redo()
|
|
1091
|
+
print(doc.content) # "Hello, World"
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
**使用场景**: 文本编辑器撤销/重做、事务回滚、宏录制、任务队列、CLI命令执行。
|
|
1095
|
+
|
|
1096
|
+
**注意事项**:
|
|
1097
|
+
- 简单场景可以用callable替代Command类
|
|
1098
|
+
- 命令应捕获执行时的状态以支持可靠的undo
|
|
1099
|
+
- 注意命令与Memento模式结合以保存完整快照
|
|
1100
|
+
|
|
1101
|
+
### 4. 状态机模式 (State Machine)
|
|
1102
|
+
|
|
1103
|
+
**问题描述**: 允许对象在其内部状态改变时改变其行为。
|
|
1104
|
+
|
|
1105
|
+
**Python实现**:
|
|
1106
|
+
|
|
1107
|
+
```python
|
|
1108
|
+
from __future__ import annotations
|
|
1109
|
+
from enum import Enum, auto
|
|
1110
|
+
from dataclasses import dataclass, field
|
|
1111
|
+
from typing import Callable
|
|
1112
|
+
|
|
1113
|
+
class OrderStatus(Enum):
|
|
1114
|
+
PENDING = auto()
|
|
1115
|
+
PAID = auto()
|
|
1116
|
+
SHIPPED = auto()
|
|
1117
|
+
DELIVERED = auto()
|
|
1118
|
+
CANCELLED = auto()
|
|
1119
|
+
|
|
1120
|
+
# 状态转换定义:(当前状态, 事件) -> 下一个状态
|
|
1121
|
+
type TransitionKey = tuple[OrderStatus, str]
|
|
1122
|
+
|
|
1123
|
+
_TRANSITIONS: dict[TransitionKey, OrderStatus] = {
|
|
1124
|
+
(OrderStatus.PENDING, "pay"): OrderStatus.PAID,
|
|
1125
|
+
(OrderStatus.PENDING, "cancel"): OrderStatus.CANCELLED,
|
|
1126
|
+
(OrderStatus.PAID, "ship"): OrderStatus.SHIPPED,
|
|
1127
|
+
(OrderStatus.PAID, "cancel"): OrderStatus.CANCELLED,
|
|
1128
|
+
(OrderStatus.SHIPPED, "deliver"): OrderStatus.DELIVERED,
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
type HookFn = Callable[["OrderStateMachine", str], None]
|
|
1132
|
+
|
|
1133
|
+
@dataclass
|
|
1134
|
+
class OrderStateMachine:
|
|
1135
|
+
order_id: str
|
|
1136
|
+
status: OrderStatus = OrderStatus.PENDING
|
|
1137
|
+
_on_transition: list[HookFn] = field(default_factory=list)
|
|
1138
|
+
|
|
1139
|
+
def add_hook(self, hook: HookFn) -> None:
|
|
1140
|
+
self._on_transition.append(hook)
|
|
1141
|
+
|
|
1142
|
+
def trigger(self, event: str) -> None:
|
|
1143
|
+
key = (self.status, event)
|
|
1144
|
+
next_status = _TRANSITIONS.get(key)
|
|
1145
|
+
if next_status is None:
|
|
1146
|
+
raise ValueError(
|
|
1147
|
+
f"Invalid transition: {self.status.name} + '{event}'"
|
|
1148
|
+
)
|
|
1149
|
+
old = self.status
|
|
1150
|
+
self.status = next_status
|
|
1151
|
+
for hook in self._on_transition:
|
|
1152
|
+
hook(self, f"{old.name} -> {next_status.name}")
|
|
1153
|
+
|
|
1154
|
+
@property
|
|
1155
|
+
def is_terminal(self) -> bool:
|
|
1156
|
+
return self.status in {OrderStatus.DELIVERED, OrderStatus.CANCELLED}
|
|
1157
|
+
|
|
1158
|
+
# 使用
|
|
1159
|
+
order = OrderStateMachine("ORD-001")
|
|
1160
|
+
order.add_hook(lambda sm, t: print(f"[{sm.order_id}] {t}"))
|
|
1161
|
+
order.trigger("pay") # [ORD-001] PENDING -> PAID
|
|
1162
|
+
order.trigger("ship") # [ORD-001] PAID -> SHIPPED
|
|
1163
|
+
order.trigger("deliver") # [ORD-001] SHIPPED -> DELIVERED
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
**使用场景**: 订单流程、工作流引擎、游戏AI、协议解析、审批流程。
|
|
1167
|
+
|
|
1168
|
+
**注意事项**:
|
|
1169
|
+
- 简单状态机用字典映射比类层次更清晰
|
|
1170
|
+
- 复杂场景考虑`transitions`库或`statemachine`库
|
|
1171
|
+
- 状态转换必须是显式的——不允许隐式跳跃
|
|
1172
|
+
|
|
1173
|
+
### 5. 模板方法模式 (Template Method)
|
|
1174
|
+
|
|
1175
|
+
**问题描述**: 定义算法骨架,将某些步骤延迟到子类实现。
|
|
1176
|
+
|
|
1177
|
+
**Python实现**:
|
|
1178
|
+
|
|
1179
|
+
```python
|
|
1180
|
+
from abc import ABC, abstractmethod
|
|
1181
|
+
from dataclasses import dataclass
|
|
1182
|
+
import time
|
|
1183
|
+
|
|
1184
|
+
@dataclass
|
|
1185
|
+
class ETLResult:
|
|
1186
|
+
records_extracted: int
|
|
1187
|
+
records_transformed: int
|
|
1188
|
+
records_loaded: int
|
|
1189
|
+
elapsed_seconds: float
|
|
1190
|
+
|
|
1191
|
+
class ETLPipeline(ABC):
|
|
1192
|
+
"""ETL管道模板:固定的提取->转换->加载流程"""
|
|
1193
|
+
def run(self) -> ETLResult:
|
|
1194
|
+
start = time.time()
|
|
1195
|
+
raw = self.extract()
|
|
1196
|
+
transformed = self.transform(raw)
|
|
1197
|
+
count = self.load(transformed)
|
|
1198
|
+
elapsed = time.time() - start
|
|
1199
|
+
self.on_complete(count, elapsed) # 钩子方法
|
|
1200
|
+
return ETLResult(
|
|
1201
|
+
records_extracted=len(raw),
|
|
1202
|
+
records_transformed=len(transformed),
|
|
1203
|
+
records_loaded=count,
|
|
1204
|
+
elapsed_seconds=elapsed,
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
@abstractmethod
|
|
1208
|
+
def extract(self) -> list[dict]: ...
|
|
1209
|
+
|
|
1210
|
+
@abstractmethod
|
|
1211
|
+
def transform(self, raw: list[dict]) -> list[dict]: ...
|
|
1212
|
+
|
|
1213
|
+
@abstractmethod
|
|
1214
|
+
def load(self, data: list[dict]) -> int: ...
|
|
1215
|
+
|
|
1216
|
+
def on_complete(self, count: int, elapsed: float) -> None:
|
|
1217
|
+
"""钩子方法:子类可选覆写"""
|
|
1218
|
+
pass
|
|
1219
|
+
|
|
1220
|
+
class CSVToPostgresETL(ETLPipeline):
|
|
1221
|
+
def __init__(self, csv_path: str, table: str) -> None:
|
|
1222
|
+
self.csv_path = csv_path
|
|
1223
|
+
self.table = table
|
|
1224
|
+
|
|
1225
|
+
def extract(self) -> list[dict]:
|
|
1226
|
+
print(f"Reading CSV from {self.csv_path}")
|
|
1227
|
+
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
|
|
1228
|
+
|
|
1229
|
+
def transform(self, raw: list[dict]) -> list[dict]:
|
|
1230
|
+
return [
|
|
1231
|
+
{**row, "name": row["name"].strip().title()}
|
|
1232
|
+
for row in raw
|
|
1233
|
+
]
|
|
1234
|
+
|
|
1235
|
+
def load(self, data: list[dict]) -> int:
|
|
1236
|
+
print(f"Inserting {len(data)} rows into {self.table}")
|
|
1237
|
+
return len(data)
|
|
1238
|
+
|
|
1239
|
+
def on_complete(self, count: int, elapsed: float) -> None:
|
|
1240
|
+
print(f"ETL complete: {count} records in {elapsed:.2f}s")
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
**使用场景**: ETL管道、测试框架(setUp/tearDown)、构建系统、报告生成器。
|
|
1244
|
+
|
|
1245
|
+
**注意事项**:
|
|
1246
|
+
- Python也可以用函数参数替代子类化实现模板方法
|
|
1247
|
+
- 钩子方法提供默认空实现,子类按需覆写
|
|
1248
|
+
- 避免模板方法嵌套太深——超过5个步骤考虑拆分
|
|
1249
|
+
|
|
1250
|
+
### 6. 迭代器模式 (Iterator)
|
|
1251
|
+
|
|
1252
|
+
**问题描述**: 提供一种顺序访问聚合对象元素的方法,而不暴露其底层表示。
|
|
1253
|
+
|
|
1254
|
+
**Python实现**:
|
|
1255
|
+
|
|
1256
|
+
```python
|
|
1257
|
+
from typing import Iterator, Generator
|
|
1258
|
+
from dataclasses import dataclass
|
|
1259
|
+
|
|
1260
|
+
@dataclass
|
|
1261
|
+
class Pagination:
|
|
1262
|
+
"""分页迭代器:逐页获取远程数据"""
|
|
1263
|
+
base_url: str
|
|
1264
|
+
page_size: int = 20
|
|
1265
|
+
|
|
1266
|
+
def fetch_page(self, page: int) -> list[dict]:
|
|
1267
|
+
"""模拟远程API分页请求"""
|
|
1268
|
+
if page > 5: # 模拟5页数据
|
|
1269
|
+
return []
|
|
1270
|
+
return [
|
|
1271
|
+
{"id": (page - 1) * self.page_size + i, "data": f"item-{i}"}
|
|
1272
|
+
for i in range(self.page_size)
|
|
1273
|
+
]
|
|
1274
|
+
|
|
1275
|
+
def __iter__(self) -> Generator[dict, None, None]:
|
|
1276
|
+
"""生成器实现:逐条yield,惰性加载每页"""
|
|
1277
|
+
page = 1
|
|
1278
|
+
while True:
|
|
1279
|
+
items = self.fetch_page(page)
|
|
1280
|
+
if not items:
|
|
1281
|
+
break
|
|
1282
|
+
yield from items
|
|
1283
|
+
page += 1
|
|
1284
|
+
|
|
1285
|
+
def chunked(iterable, size: int) -> Generator[list, None, None]:
|
|
1286
|
+
"""通用分块迭代器"""
|
|
1287
|
+
chunk: list = []
|
|
1288
|
+
for item in iterable:
|
|
1289
|
+
chunk.append(item)
|
|
1290
|
+
if len(chunk) == size:
|
|
1291
|
+
yield chunk
|
|
1292
|
+
chunk = []
|
|
1293
|
+
if chunk:
|
|
1294
|
+
yield chunk
|
|
1295
|
+
|
|
1296
|
+
# 使用
|
|
1297
|
+
paginator = Pagination("https://api.example.com/items", page_size=20)
|
|
1298
|
+
for item in paginator:
|
|
1299
|
+
if item["id"] > 10:
|
|
1300
|
+
break
|
|
1301
|
+
print(item)
|
|
1302
|
+
|
|
1303
|
+
# 分块处理
|
|
1304
|
+
for batch in chunked(range(25), 10):
|
|
1305
|
+
print(f"Processing batch of {len(batch)} items")
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
**使用场景**: 分页API遍历、大文件逐行读取、数据库游标、树遍历、流式处理。
|
|
1309
|
+
|
|
1310
|
+
**注意事项**:
|
|
1311
|
+
- Python的生成器(`yield`)是迭代器的语法糖,优先使用
|
|
1312
|
+
- `yield from`委托子迭代器,避免手动循环
|
|
1313
|
+
- 迭代器是一次性的——需要多次遍历要重新创建或使用`itertools.tee`
|
|
1314
|
+
|
|
1315
|
+
### 7. 责任链模式 (Chain of Responsibility)
|
|
1316
|
+
|
|
1317
|
+
**问题描述**: 将请求沿着处理者链传递,直到有一个处理者处理它。
|
|
1318
|
+
|
|
1319
|
+
**Python实现**:
|
|
1320
|
+
|
|
1321
|
+
```python
|
|
1322
|
+
from __future__ import annotations
|
|
1323
|
+
from typing import Callable, Any
|
|
1324
|
+
from dataclasses import dataclass
|
|
1325
|
+
|
|
1326
|
+
# Python惯用:中间件/管道式责任链
|
|
1327
|
+
type Middleware = Callable[[dict[str, Any], Callable], dict[str, Any]]
|
|
1328
|
+
|
|
1329
|
+
def auth_middleware(request: dict[str, Any], next_handler: Callable) -> dict[str, Any]:
|
|
1330
|
+
token = request.get("headers", {}).get("Authorization")
|
|
1331
|
+
if not token:
|
|
1332
|
+
return {"status": 401, "body": "Unauthorized"}
|
|
1333
|
+
if not token.startswith("Bearer "):
|
|
1334
|
+
return {"status": 401, "body": "Invalid token format"}
|
|
1335
|
+
request["user"] = {"id": 1, "role": "admin"} # 模拟解析token
|
|
1336
|
+
return next_handler(request)
|
|
1337
|
+
|
|
1338
|
+
def rate_limit_middleware(request: dict[str, Any], next_handler: Callable) -> dict[str, Any]:
|
|
1339
|
+
client_ip = request.get("client_ip", "unknown")
|
|
1340
|
+
# 模拟限流检查
|
|
1341
|
+
if client_ip == "banned":
|
|
1342
|
+
return {"status": 429, "body": "Rate limit exceeded"}
|
|
1343
|
+
return next_handler(request)
|
|
1344
|
+
|
|
1345
|
+
def logging_middleware(request: dict[str, Any], next_handler: Callable) -> dict[str, Any]:
|
|
1346
|
+
print(f"[LOG] {request.get('method', 'GET')} {request.get('path', '/')}")
|
|
1347
|
+
response = next_handler(request)
|
|
1348
|
+
print(f"[LOG] Response: {response.get('status', 200)}")
|
|
1349
|
+
return response
|
|
1350
|
+
|
|
1351
|
+
class MiddlewarePipeline:
|
|
1352
|
+
"""中间件管道:按顺序组装处理链"""
|
|
1353
|
+
def __init__(self) -> None:
|
|
1354
|
+
self._middlewares: list[Middleware] = []
|
|
1355
|
+
|
|
1356
|
+
def use(self, middleware: Middleware) -> "MiddlewarePipeline":
|
|
1357
|
+
self._middlewares.append(middleware)
|
|
1358
|
+
return self
|
|
1359
|
+
|
|
1360
|
+
def handle(self, request: dict[str, Any], final: Callable) -> dict[str, Any]:
|
|
1361
|
+
"""构建责任链并执行"""
|
|
1362
|
+
handler = final
|
|
1363
|
+
for mw in reversed(self._middlewares):
|
|
1364
|
+
prev_handler = handler
|
|
1365
|
+
handler = lambda req, _mw=mw, _next=prev_handler: _mw(req, _next)
|
|
1366
|
+
return handler(request)
|
|
1367
|
+
|
|
1368
|
+
# 使用
|
|
1369
|
+
pipeline = MiddlewarePipeline()
|
|
1370
|
+
pipeline.use(logging_middleware).use(rate_limit_middleware).use(auth_middleware)
|
|
1371
|
+
|
|
1372
|
+
def handle_request(request: dict[str, Any]) -> dict[str, Any]:
|
|
1373
|
+
return {"status": 200, "body": f"Hello, user {request['user']['id']}"}
|
|
1374
|
+
|
|
1375
|
+
response = pipeline.handle(
|
|
1376
|
+
{"method": "GET", "path": "/api/data", "headers": {"Authorization": "Bearer abc123"}},
|
|
1377
|
+
handle_request,
|
|
1378
|
+
)
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
**使用场景**: HTTP中间件、日志处理链、审批流程、输入验证管道、异常处理层。
|
|
1382
|
+
|
|
1383
|
+
**注意事项**:
|
|
1384
|
+
- Python中间件模式(WSGI/ASGI)就是责任链的典型应用
|
|
1385
|
+
- 注意闭包变量捕获问题(使用默认参数绑定)
|
|
1386
|
+
- 链中的每个处理者应该只关注自己的职责
|
|
1387
|
+
|
|
1388
|
+
---
|
|
1389
|
+
|
|
1390
|
+
## Python特有模式
|
|
1391
|
+
|
|
1392
|
+
### 1. Mixin模式
|
|
1393
|
+
|
|
1394
|
+
**问题描述**: 通过多重继承为类添加可复用的功能片段,而不建立is-a关系。
|
|
1395
|
+
|
|
1396
|
+
**Python实现**:
|
|
1397
|
+
|
|
1398
|
+
```python
|
|
1399
|
+
import json
|
|
1400
|
+
import time
|
|
1401
|
+
from typing import Any
|
|
1402
|
+
|
|
1403
|
+
class SerializableMixin:
|
|
1404
|
+
"""JSON序列化能力"""
|
|
1405
|
+
def to_json(self) -> str:
|
|
1406
|
+
data = {}
|
|
1407
|
+
for key, value in self.__dict__.items():
|
|
1408
|
+
if not key.startswith("_"):
|
|
1409
|
+
data[key] = value
|
|
1410
|
+
return json.dumps(data, default=str, ensure_ascii=False)
|
|
1411
|
+
|
|
1412
|
+
@classmethod
|
|
1413
|
+
def from_json(cls, json_str: str) -> "SerializableMixin":
|
|
1414
|
+
data = json.loads(json_str)
|
|
1415
|
+
instance = cls.__new__(cls)
|
|
1416
|
+
instance.__dict__.update(data)
|
|
1417
|
+
return instance
|
|
1418
|
+
|
|
1419
|
+
class TimestampMixin:
|
|
1420
|
+
"""自动时间戳追踪"""
|
|
1421
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
1422
|
+
super().__init_subclass__(**kwargs)
|
|
1423
|
+
original_init = cls.__init__
|
|
1424
|
+
|
|
1425
|
+
def new_init(self: Any, *args: Any, **kw: Any) -> None:
|
|
1426
|
+
original_init(self, *args, **kw)
|
|
1427
|
+
self.created_at = time.time()
|
|
1428
|
+
self.updated_at = time.time()
|
|
1429
|
+
|
|
1430
|
+
cls.__init__ = new_init # type: ignore[attr-defined]
|
|
1431
|
+
|
|
1432
|
+
def touch(self) -> None:
|
|
1433
|
+
self.updated_at = time.time()
|
|
1434
|
+
|
|
1435
|
+
class ValidatableMixin:
|
|
1436
|
+
"""声明式字段验证"""
|
|
1437
|
+
_validators: dict[str, list] = {}
|
|
1438
|
+
|
|
1439
|
+
def validate(self) -> list[str]:
|
|
1440
|
+
errors: list[str] = []
|
|
1441
|
+
for field_name, validators in self._validators.items():
|
|
1442
|
+
value = getattr(self, field_name, None)
|
|
1443
|
+
for validator_fn, msg in validators:
|
|
1444
|
+
if not validator_fn(value):
|
|
1445
|
+
errors.append(f"{field_name}: {msg}")
|
|
1446
|
+
return errors
|
|
1447
|
+
|
|
1448
|
+
class User(SerializableMixin, TimestampMixin, ValidatableMixin):
|
|
1449
|
+
_validators = {
|
|
1450
|
+
"name": [
|
|
1451
|
+
(lambda v: v and len(v) >= 2, "Name must be at least 2 characters"),
|
|
1452
|
+
],
|
|
1453
|
+
"email": [
|
|
1454
|
+
(lambda v: v and "@" in v, "Invalid email format"),
|
|
1455
|
+
],
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
def __init__(self, name: str, email: str) -> None:
|
|
1459
|
+
self.name = name
|
|
1460
|
+
self.email = email
|
|
1461
|
+
|
|
1462
|
+
# 使用
|
|
1463
|
+
user = User("Alice", "alice@example.com")
|
|
1464
|
+
print(user.to_json()) # 序列化
|
|
1465
|
+
print(user.created_at) # 时间戳
|
|
1466
|
+
print(user.validate()) # 验证 -> []
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
**使用场景**: ORM模型增强、API序列化、审计日志、权限控制、缓存能力注入。
|
|
1470
|
+
|
|
1471
|
+
**注意事项**:
|
|
1472
|
+
- Mixin类不应独立实例化,且不应有`__init__`(除非通过`__init_subclass__`)
|
|
1473
|
+
- MRO(方法解析顺序)从左到右——将最具体的Mixin放在最左边
|
|
1474
|
+
- 避免Mixin之间有方法名冲突
|
|
1475
|
+
|
|
1476
|
+
### 2. 描述符模式 (Descriptor)
|
|
1477
|
+
|
|
1478
|
+
**问题描述**: 通过定义`__get__`、`__set__`、`__delete__`方法控制属性访问行为。
|
|
1479
|
+
|
|
1480
|
+
**Python实现**:
|
|
1481
|
+
|
|
1482
|
+
```python
|
|
1483
|
+
from typing import Any, Callable
|
|
1484
|
+
|
|
1485
|
+
class Validated:
|
|
1486
|
+
"""通用验证描述符"""
|
|
1487
|
+
def __init__(self, validator: Callable[[Any], bool], error_msg: str) -> None:
|
|
1488
|
+
self.validator = validator
|
|
1489
|
+
self.error_msg = error_msg
|
|
1490
|
+
self.attr_name = ""
|
|
1491
|
+
|
|
1492
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
|
1493
|
+
self.attr_name = name
|
|
1494
|
+
self.storage_name = f"_desc_{name}"
|
|
1495
|
+
|
|
1496
|
+
def __get__(self, obj: Any, objtype: type | None = None) -> Any:
|
|
1497
|
+
if obj is None:
|
|
1498
|
+
return self
|
|
1499
|
+
return getattr(obj, self.storage_name, None)
|
|
1500
|
+
|
|
1501
|
+
def __set__(self, obj: Any, value: Any) -> None:
|
|
1502
|
+
if not self.validator(value):
|
|
1503
|
+
raise ValueError(f"{self.attr_name}: {self.error_msg} (got {value!r})")
|
|
1504
|
+
setattr(obj, self.storage_name, value)
|
|
1505
|
+
|
|
1506
|
+
class PositiveNumber(Validated):
|
|
1507
|
+
def __init__(self, error_msg: str = "must be positive") -> None:
|
|
1508
|
+
super().__init__(lambda v: isinstance(v, (int, float)) and v > 0, error_msg)
|
|
1509
|
+
|
|
1510
|
+
class NonEmptyString(Validated):
|
|
1511
|
+
def __init__(self, max_length: int = 255) -> None:
|
|
1512
|
+
super().__init__(
|
|
1513
|
+
lambda v: isinstance(v, str) and 0 < len(v) <= max_length,
|
|
1514
|
+
f"must be non-empty string (max {max_length} chars)",
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
class InRange(Validated):
|
|
1518
|
+
def __init__(self, min_val: float, max_val: float) -> None:
|
|
1519
|
+
super().__init__(
|
|
1520
|
+
lambda v: isinstance(v, (int, float)) and min_val <= v <= max_val,
|
|
1521
|
+
f"must be between {min_val} and {max_val}",
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
class Product:
|
|
1525
|
+
name = NonEmptyString(max_length=100)
|
|
1526
|
+
price = PositiveNumber()
|
|
1527
|
+
rating = InRange(0.0, 5.0)
|
|
1528
|
+
|
|
1529
|
+
def __init__(self, name: str, price: float, rating: float) -> None:
|
|
1530
|
+
self.name = name
|
|
1531
|
+
self.price = price
|
|
1532
|
+
self.rating = rating
|
|
1533
|
+
|
|
1534
|
+
# 使用
|
|
1535
|
+
p = Product("Widget", 9.99, 4.5) # OK
|
|
1536
|
+
# Product("", 9.99, 4.5) # ValueError: name: must be non-empty string
|
|
1537
|
+
# Product("Widget", -1, 4.5) # ValueError: price: must be positive
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
**使用场景**: ORM字段定义、表单验证、属性缓存(`cached_property`就是描述符)、类型强制。
|
|
1541
|
+
|
|
1542
|
+
**注意事项**:
|
|
1543
|
+
- `__set_name__`(Python 3.6+)自动获取属性名,不再需要手动传入
|
|
1544
|
+
- 描述符存储值时避免用实例`__dict__`的同名键(会遮蔽描述符)
|
|
1545
|
+
- `property`本身就是描述符的语法糖
|
|
1546
|
+
|
|
1547
|
+
### 3. 元类模式 (Metaclass)
|
|
1548
|
+
|
|
1549
|
+
**问题描述**: 控制类的创建过程,实现类级别的约束和自动化。
|
|
1550
|
+
|
|
1551
|
+
**Python实现**:
|
|
1552
|
+
|
|
1553
|
+
```python
|
|
1554
|
+
from typing import Any
|
|
1555
|
+
|
|
1556
|
+
class PluginMeta(type):
|
|
1557
|
+
"""插件注册元类:所有子类自动注册到注册表"""
|
|
1558
|
+
_registry: dict[str, type] = {}
|
|
1559
|
+
|
|
1560
|
+
def __new__(
|
|
1561
|
+
mcs,
|
|
1562
|
+
name: str,
|
|
1563
|
+
bases: tuple[type, ...],
|
|
1564
|
+
namespace: dict[str, Any],
|
|
1565
|
+
**kwargs: Any,
|
|
1566
|
+
) -> "PluginMeta":
|
|
1567
|
+
cls = super().__new__(mcs, name, bases, namespace)
|
|
1568
|
+
# 不注册基类本身
|
|
1569
|
+
if bases:
|
|
1570
|
+
plugin_name = getattr(cls, "plugin_name", name.lower())
|
|
1571
|
+
mcs._registry[plugin_name] = cls
|
|
1572
|
+
return cls
|
|
1573
|
+
|
|
1574
|
+
@classmethod
|
|
1575
|
+
def get_plugin(mcs, name: str) -> type | None:
|
|
1576
|
+
return mcs._registry.get(name)
|
|
1577
|
+
|
|
1578
|
+
@classmethod
|
|
1579
|
+
def list_plugins(mcs) -> list[str]:
|
|
1580
|
+
return list(mcs._registry.keys())
|
|
1581
|
+
|
|
1582
|
+
class Plugin(metaclass=PluginMeta):
|
|
1583
|
+
"""插件基类"""
|
|
1584
|
+
plugin_name: str
|
|
1585
|
+
|
|
1586
|
+
def execute(self, data: dict) -> dict:
|
|
1587
|
+
raise NotImplementedError
|
|
1588
|
+
|
|
1589
|
+
class JSONExporter(Plugin):
|
|
1590
|
+
plugin_name = "json_exporter"
|
|
1591
|
+
def execute(self, data: dict) -> dict:
|
|
1592
|
+
return {"format": "json", "output": str(data)}
|
|
1593
|
+
|
|
1594
|
+
class CSVExporter(Plugin):
|
|
1595
|
+
plugin_name = "csv_exporter"
|
|
1596
|
+
def execute(self, data: dict) -> dict:
|
|
1597
|
+
header = ",".join(data.keys())
|
|
1598
|
+
values = ",".join(str(v) for v in data.values())
|
|
1599
|
+
return {"format": "csv", "output": f"{header}\n{values}"}
|
|
1600
|
+
|
|
1601
|
+
# 使用:自动发现所有已注册的插件
|
|
1602
|
+
print(PluginMeta.list_plugins()) # ['json_exporter', 'csv_exporter']
|
|
1603
|
+
plugin_cls = PluginMeta.get_plugin("json_exporter")
|
|
1604
|
+
if plugin_cls:
|
|
1605
|
+
result = plugin_cls().execute({"name": "Alice", "age": 30})
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
**使用场景**: 插件系统、ORM模型注册、API路由自动收集、接口合约强制。
|
|
1609
|
+
|
|
1610
|
+
**注意事项**:
|
|
1611
|
+
- 元类是Python最强大的工具,也最容易滥用
|
|
1612
|
+
- **优先考虑`__init_subclass__`**(Python 3.6+),它能覆盖大部分元类场景且更简单
|
|
1613
|
+
- 避免多层元类继承——极难调试
|
|
1614
|
+
- Django ORM和SQLAlchemy大量使用元类,但业务代码很少需要
|
|
1615
|
+
|
|
1616
|
+
### 4. 上下文管理器协议 (Context Manager)
|
|
1617
|
+
|
|
1618
|
+
**问题描述**: 确保资源的正确获取和释放,即使发生异常。
|
|
1619
|
+
|
|
1620
|
+
**Python实现**:
|
|
1621
|
+
|
|
1622
|
+
```python
|
|
1623
|
+
from __future__ import annotations
|
|
1624
|
+
import time
|
|
1625
|
+
from contextlib import contextmanager
|
|
1626
|
+
from typing import Generator, Any
|
|
1627
|
+
from dataclasses import dataclass, field
|
|
1628
|
+
|
|
1629
|
+
# 方式一:类实现
|
|
1630
|
+
@dataclass
|
|
1631
|
+
class DatabaseTransaction:
|
|
1632
|
+
"""数据库事务上下文管理器"""
|
|
1633
|
+
connection: Any
|
|
1634
|
+
_savepoint: str | None = field(default=None, init=False)
|
|
1635
|
+
|
|
1636
|
+
def __enter__(self) -> "DatabaseTransaction":
|
|
1637
|
+
self._savepoint = f"sp_{id(self)}"
|
|
1638
|
+
print(f"BEGIN TRANSACTION (savepoint: {self._savepoint})")
|
|
1639
|
+
return self
|
|
1640
|
+
|
|
1641
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
1642
|
+
if exc_type is not None:
|
|
1643
|
+
print(f"ROLLBACK to {self._savepoint}: {exc_val}")
|
|
1644
|
+
# 返回True表示异常已处理,不再传播
|
|
1645
|
+
return False
|
|
1646
|
+
print("COMMIT")
|
|
1647
|
+
return False
|
|
1648
|
+
|
|
1649
|
+
def execute(self, sql: str) -> list[dict]:
|
|
1650
|
+
print(f" EXEC: {sql}")
|
|
1651
|
+
return []
|
|
1652
|
+
|
|
1653
|
+
# 方式二:生成器实现(更简洁)
|
|
1654
|
+
@contextmanager
|
|
1655
|
+
def timer(label: str) -> Generator[dict[str, float], None, None]:
|
|
1656
|
+
"""计时上下文管理器"""
|
|
1657
|
+
result: dict[str, float] = {}
|
|
1658
|
+
start = time.perf_counter()
|
|
1659
|
+
try:
|
|
1660
|
+
yield result
|
|
1661
|
+
finally:
|
|
1662
|
+
result["elapsed"] = time.perf_counter() - start
|
|
1663
|
+
print(f"[{label}] {result['elapsed']:.4f}s")
|
|
1664
|
+
|
|
1665
|
+
@contextmanager
|
|
1666
|
+
def temporary_env(**variables: str) -> Generator[None, None, None]:
|
|
1667
|
+
"""临时设置环境变量,退出时恢复"""
|
|
1668
|
+
import os
|
|
1669
|
+
old_values: dict[str, str | None] = {}
|
|
1670
|
+
try:
|
|
1671
|
+
for key, value in variables.items():
|
|
1672
|
+
old_values[key] = os.environ.get(key)
|
|
1673
|
+
os.environ[key] = value
|
|
1674
|
+
yield
|
|
1675
|
+
finally:
|
|
1676
|
+
for key, old_value in old_values.items():
|
|
1677
|
+
if old_value is None:
|
|
1678
|
+
os.environ.pop(key, None)
|
|
1679
|
+
else:
|
|
1680
|
+
os.environ[key] = old_value
|
|
1681
|
+
|
|
1682
|
+
# 使用
|
|
1683
|
+
with timer("data_load") as t:
|
|
1684
|
+
time.sleep(0.1)
|
|
1685
|
+
print(f"Elapsed: {t['elapsed']:.4f}s")
|
|
1686
|
+
|
|
1687
|
+
with temporary_env(DATABASE_URL="sqlite:///test.db", DEBUG="1"):
|
|
1688
|
+
import os
|
|
1689
|
+
print(os.environ["DATABASE_URL"]) # sqlite:///test.db
|
|
1690
|
+
# 退出后环境变量自动恢复
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
**使用场景**: 文件/数据库/网络连接管理、锁管理、临时状态、事务、测试fixture。
|
|
1694
|
+
|
|
1695
|
+
**注意事项**:
|
|
1696
|
+
- `@contextmanager`适合简单场景,复杂逻辑用类实现
|
|
1697
|
+
- `__exit__`返回`True`会吞掉异常——除非明确需要,否则返回`False`
|
|
1698
|
+
- 支持`async with`需要实现`__aenter__`/`__aexit__`
|
|
1699
|
+
|
|
1700
|
+
### 5. 协程模式 (Coroutine Patterns)
|
|
1701
|
+
|
|
1702
|
+
**问题描述**: 利用async/await实现高效的并发模式。
|
|
1703
|
+
|
|
1704
|
+
**Python实现**:
|
|
1705
|
+
|
|
1706
|
+
```python
|
|
1707
|
+
import asyncio
|
|
1708
|
+
from typing import AsyncIterator
|
|
1709
|
+
|
|
1710
|
+
# 异步生产者-消费者模式
|
|
1711
|
+
async def producer(queue: asyncio.Queue[str | None], items: list[str]) -> None:
|
|
1712
|
+
for item in items:
|
|
1713
|
+
await asyncio.sleep(0.1) # 模拟异步IO
|
|
1714
|
+
await queue.put(item)
|
|
1715
|
+
print(f"Produced: {item}")
|
|
1716
|
+
await queue.put(None) # 哨兵值表示结束
|
|
1717
|
+
|
|
1718
|
+
async def consumer(queue: asyncio.Queue[str | None], name: str) -> list[str]:
|
|
1719
|
+
results: list[str] = []
|
|
1720
|
+
while True:
|
|
1721
|
+
item = await queue.get()
|
|
1722
|
+
if item is None:
|
|
1723
|
+
await queue.put(None) # 传递结束信号给其他消费者
|
|
1724
|
+
break
|
|
1725
|
+
print(f"[{name}] Consumed: {item}")
|
|
1726
|
+
results.append(item)
|
|
1727
|
+
queue.task_done()
|
|
1728
|
+
return results
|
|
1729
|
+
|
|
1730
|
+
# 异步迭代器模式
|
|
1731
|
+
async def async_paginate(url: str, page_size: int = 10) -> AsyncIterator[dict]:
|
|
1732
|
+
"""异步分页迭代器"""
|
|
1733
|
+
page = 1
|
|
1734
|
+
while True:
|
|
1735
|
+
# 模拟异步API调用
|
|
1736
|
+
await asyncio.sleep(0.05)
|
|
1737
|
+
items = [{"id": (page - 1) * page_size + i} for i in range(page_size)]
|
|
1738
|
+
if page > 3: # 模拟数据结束
|
|
1739
|
+
break
|
|
1740
|
+
for item in items:
|
|
1741
|
+
yield item
|
|
1742
|
+
page += 1
|
|
1743
|
+
|
|
1744
|
+
# 扇出/扇入模式
|
|
1745
|
+
async def fetch_all_parallel(urls: list[str]) -> list[dict]:
|
|
1746
|
+
"""并行获取多个URL,收集结果"""
|
|
1747
|
+
async def fetch_one(url: str) -> dict:
|
|
1748
|
+
await asyncio.sleep(0.1) # 模拟网络请求
|
|
1749
|
+
return {"url": url, "status": 200}
|
|
1750
|
+
|
|
1751
|
+
tasks = [asyncio.create_task(fetch_one(url)) for url in urls]
|
|
1752
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
1753
|
+
return [r for r in results if isinstance(r, dict)]
|
|
1754
|
+
|
|
1755
|
+
# 使用
|
|
1756
|
+
async def main() -> None:
|
|
1757
|
+
# 生产者-消费者
|
|
1758
|
+
queue: asyncio.Queue[str | None] = asyncio.Queue(maxsize=5)
|
|
1759
|
+
items = [f"task-{i}" for i in range(10)]
|
|
1760
|
+
await asyncio.gather(
|
|
1761
|
+
producer(queue, items),
|
|
1762
|
+
consumer(queue, "worker-1"),
|
|
1763
|
+
consumer(queue, "worker-2"),
|
|
1764
|
+
)
|
|
1765
|
+
|
|
1766
|
+
# 异步迭代
|
|
1767
|
+
async for item in async_paginate("https://api.example.com"):
|
|
1768
|
+
if item["id"] > 5:
|
|
1769
|
+
break
|
|
1770
|
+
|
|
1771
|
+
asyncio.run(main())
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
**使用场景**: Web爬虫、API聚合、实时数据流处理、WebSocket服务。
|
|
1775
|
+
|
|
1776
|
+
**注意事项**:
|
|
1777
|
+
- 不要在async函数中调用阻塞IO——使用`run_in_executor`包装
|
|
1778
|
+
- `asyncio.gather`的`return_exceptions=True`防止一个失败导致全部取消
|
|
1779
|
+
- 使用`asyncio.Semaphore`限制并发度
|
|
1780
|
+
|
|
1781
|
+
### 6. 依赖注入模式 (Dependency Injection)
|
|
1782
|
+
|
|
1783
|
+
**问题描述**: 将依赖的创建与使用分离,提高可测试性和灵活性。
|
|
1784
|
+
|
|
1785
|
+
**Python实现**:
|
|
1786
|
+
|
|
1787
|
+
```python
|
|
1788
|
+
from __future__ import annotations
|
|
1789
|
+
from typing import Protocol, Callable, Any, TypeVar
|
|
1790
|
+
from dataclasses import dataclass, field
|
|
1791
|
+
|
|
1792
|
+
T = TypeVar("T")
|
|
1793
|
+
|
|
1794
|
+
class Repository(Protocol):
|
|
1795
|
+
def find_by_id(self, entity_id: str) -> dict | None: ...
|
|
1796
|
+
def save(self, entity: dict) -> None: ...
|
|
1797
|
+
|
|
1798
|
+
class NotificationSender(Protocol):
|
|
1799
|
+
def send(self, recipient: str, message: str) -> bool: ...
|
|
1800
|
+
|
|
1801
|
+
# 简易DI容器
|
|
1802
|
+
class Container:
|
|
1803
|
+
def __init__(self) -> None:
|
|
1804
|
+
self._factories: dict[type, Callable[[], Any]] = {}
|
|
1805
|
+
self._singletons: dict[type, Any] = {}
|
|
1806
|
+
|
|
1807
|
+
def register(self, interface: type, factory: Callable[[], Any], singleton: bool = False) -> None:
|
|
1808
|
+
if singleton:
|
|
1809
|
+
self._factories[interface] = factory
|
|
1810
|
+
# 延迟创建
|
|
1811
|
+
else:
|
|
1812
|
+
self._factories[interface] = factory
|
|
1813
|
+
|
|
1814
|
+
def register_singleton(self, interface: type, factory: Callable[[], Any]) -> None:
|
|
1815
|
+
self._factories[interface] = factory
|
|
1816
|
+
|
|
1817
|
+
def resolve(self, interface: type[T]) -> T:
|
|
1818
|
+
if interface in self._singletons:
|
|
1819
|
+
return self._singletons[interface]
|
|
1820
|
+
factory = self._factories.get(interface)
|
|
1821
|
+
if factory is None:
|
|
1822
|
+
raise KeyError(f"No registration for {interface}")
|
|
1823
|
+
instance = factory()
|
|
1824
|
+
return instance
|
|
1825
|
+
|
|
1826
|
+
# 具体实现
|
|
1827
|
+
class PostgresRepository:
|
|
1828
|
+
def __init__(self, dsn: str = "postgresql://localhost/mydb") -> None:
|
|
1829
|
+
self.dsn = dsn
|
|
1830
|
+
def find_by_id(self, entity_id: str) -> dict | None:
|
|
1831
|
+
return {"id": entity_id, "data": "from postgres"}
|
|
1832
|
+
def save(self, entity: dict) -> None:
|
|
1833
|
+
print(f"Saved to Postgres: {entity}")
|
|
1834
|
+
|
|
1835
|
+
class InMemoryRepository:
|
|
1836
|
+
"""测试用内存存储"""
|
|
1837
|
+
def __init__(self) -> None:
|
|
1838
|
+
self._store: dict[str, dict] = {}
|
|
1839
|
+
def find_by_id(self, entity_id: str) -> dict | None:
|
|
1840
|
+
return self._store.get(entity_id)
|
|
1841
|
+
def save(self, entity: dict) -> None:
|
|
1842
|
+
self._store[entity["id"]] = entity
|
|
1843
|
+
|
|
1844
|
+
class EmailSender:
|
|
1845
|
+
def send(self, recipient: str, message: str) -> bool:
|
|
1846
|
+
print(f"Email to {recipient}: {message}")
|
|
1847
|
+
return True
|
|
1848
|
+
|
|
1849
|
+
@dataclass
|
|
1850
|
+
class UserService:
|
|
1851
|
+
"""通过构造函数注入依赖"""
|
|
1852
|
+
repo: Repository
|
|
1853
|
+
notifier: NotificationSender
|
|
1854
|
+
|
|
1855
|
+
def create_user(self, name: str, email: str) -> dict:
|
|
1856
|
+
user = {"id": f"user-{hash(name) % 10000}", "name": name, "email": email}
|
|
1857
|
+
self.repo.save(user)
|
|
1858
|
+
self.notifier.send(email, f"Welcome, {name}!")
|
|
1859
|
+
return user
|
|
1860
|
+
|
|
1861
|
+
# 生产环境配置
|
|
1862
|
+
container = Container()
|
|
1863
|
+
container.register(Repository, lambda: PostgresRepository())
|
|
1864
|
+
container.register(NotificationSender, lambda: EmailSender())
|
|
1865
|
+
service = UserService(
|
|
1866
|
+
repo=container.resolve(Repository),
|
|
1867
|
+
notifier=container.resolve(NotificationSender),
|
|
1868
|
+
)
|
|
1869
|
+
|
|
1870
|
+
# 测试环境:替换为mock实现
|
|
1871
|
+
test_repo = InMemoryRepository()
|
|
1872
|
+
test_service = UserService(repo=test_repo, notifier=EmailSender())
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
**使用场景**: 服务层构造、测试替身注入、配置驱动实现选择、插件系统。
|
|
1876
|
+
|
|
1877
|
+
**注意事项**:
|
|
1878
|
+
- Python中构造函数注入(传参)是最简单有效的DI方式
|
|
1879
|
+
- 避免过度工程化——大多数Python项目不需要完整的DI框架
|
|
1880
|
+
- 如需框架级DI,考虑`dependency-injector`或`inject`库
|
|
1881
|
+
|
|
1882
|
+
---
|
|
1883
|
+
|
|
1884
|
+
## 架构模式
|
|
1885
|
+
|
|
1886
|
+
### 1. 仓储模式 (Repository)
|
|
1887
|
+
|
|
1888
|
+
**问题描述**: 将数据访问逻辑与业务逻辑分离,提供集合式接口操作持久化数据。
|
|
1889
|
+
|
|
1890
|
+
**Python实现**:
|
|
1891
|
+
|
|
1892
|
+
```python
|
|
1893
|
+
from __future__ import annotations
|
|
1894
|
+
from typing import Protocol, TypeVar, Generic
|
|
1895
|
+
from dataclasses import dataclass, field
|
|
1896
|
+
from abc import abstractmethod
|
|
1897
|
+
|
|
1898
|
+
T = TypeVar("T")
|
|
1899
|
+
|
|
1900
|
+
class Repository(Protocol[T]):
|
|
1901
|
+
def get(self, entity_id: str) -> T | None: ...
|
|
1902
|
+
def list(self, offset: int = 0, limit: int = 100) -> list[T]: ...
|
|
1903
|
+
def add(self, entity: T) -> None: ...
|
|
1904
|
+
def update(self, entity: T) -> None: ...
|
|
1905
|
+
def delete(self, entity_id: str) -> bool: ...
|
|
1906
|
+
|
|
1907
|
+
@dataclass
|
|
1908
|
+
class Order:
|
|
1909
|
+
id: str
|
|
1910
|
+
customer_id: str
|
|
1911
|
+
items: list[dict] = field(default_factory=list)
|
|
1912
|
+
total: float = 0.0
|
|
1913
|
+
status: str = "pending"
|
|
1914
|
+
|
|
1915
|
+
class InMemoryOrderRepository:
|
|
1916
|
+
"""内存仓储——用于测试和原型"""
|
|
1917
|
+
def __init__(self) -> None:
|
|
1918
|
+
self._store: dict[str, Order] = {}
|
|
1919
|
+
|
|
1920
|
+
def get(self, entity_id: str) -> Order | None:
|
|
1921
|
+
return self._store.get(entity_id)
|
|
1922
|
+
|
|
1923
|
+
def list(self, offset: int = 0, limit: int = 100) -> list[Order]:
|
|
1924
|
+
orders = sorted(self._store.values(), key=lambda o: o.id)
|
|
1925
|
+
return orders[offset:offset + limit]
|
|
1926
|
+
|
|
1927
|
+
def add(self, entity: Order) -> None:
|
|
1928
|
+
if entity.id in self._store:
|
|
1929
|
+
raise ValueError(f"Order {entity.id} already exists")
|
|
1930
|
+
self._store[entity.id] = entity
|
|
1931
|
+
|
|
1932
|
+
def update(self, entity: Order) -> None:
|
|
1933
|
+
if entity.id not in self._store:
|
|
1934
|
+
raise ValueError(f"Order {entity.id} not found")
|
|
1935
|
+
self._store[entity.id] = entity
|
|
1936
|
+
|
|
1937
|
+
def delete(self, entity_id: str) -> bool:
|
|
1938
|
+
return self._store.pop(entity_id, None) is not None
|
|
1939
|
+
|
|
1940
|
+
class SQLOrderRepository:
|
|
1941
|
+
"""SQL仓储——实际生产使用"""
|
|
1942
|
+
def __init__(self, session) -> None:
|
|
1943
|
+
self._session = session
|
|
1944
|
+
|
|
1945
|
+
def get(self, entity_id: str) -> Order | None:
|
|
1946
|
+
row = self._session.execute(
|
|
1947
|
+
"SELECT * FROM orders WHERE id = :id", {"id": entity_id}
|
|
1948
|
+
).fetchone()
|
|
1949
|
+
return self._row_to_order(row) if row else None
|
|
1950
|
+
|
|
1951
|
+
def list(self, offset: int = 0, limit: int = 100) -> list[Order]:
|
|
1952
|
+
rows = self._session.execute(
|
|
1953
|
+
"SELECT * FROM orders ORDER BY id LIMIT :limit OFFSET :offset",
|
|
1954
|
+
{"limit": limit, "offset": offset},
|
|
1955
|
+
).fetchall()
|
|
1956
|
+
return [self._row_to_order(r) for r in rows]
|
|
1957
|
+
|
|
1958
|
+
def add(self, entity: Order) -> None:
|
|
1959
|
+
self._session.execute(
|
|
1960
|
+
"INSERT INTO orders (id, customer_id, total, status) VALUES (:id, :cid, :total, :status)",
|
|
1961
|
+
{"id": entity.id, "cid": entity.customer_id, "total": entity.total, "status": entity.status},
|
|
1962
|
+
)
|
|
1963
|
+
|
|
1964
|
+
def update(self, entity: Order) -> None:
|
|
1965
|
+
self._session.execute(
|
|
1966
|
+
"UPDATE orders SET total = :total, status = :status WHERE id = :id",
|
|
1967
|
+
{"id": entity.id, "total": entity.total, "status": entity.status},
|
|
1968
|
+
)
|
|
1969
|
+
|
|
1970
|
+
def delete(self, entity_id: str) -> bool:
|
|
1971
|
+
result = self._session.execute(
|
|
1972
|
+
"DELETE FROM orders WHERE id = :id", {"id": entity_id}
|
|
1973
|
+
)
|
|
1974
|
+
return result.rowcount > 0
|
|
1975
|
+
|
|
1976
|
+
@staticmethod
|
|
1977
|
+
def _row_to_order(row) -> Order:
|
|
1978
|
+
return Order(id=row.id, customer_id=row.customer_id, total=row.total, status=row.status)
|
|
1979
|
+
```
|
|
1980
|
+
|
|
1981
|
+
**使用场景**: 领域驱动设计中的聚合持久化、多数据源抽象、测试隔离。
|
|
1982
|
+
|
|
1983
|
+
**注意事项**:
|
|
1984
|
+
- 仓储接口应面向领域语言,而非SQL语义
|
|
1985
|
+
- 一个聚合根对应一个仓储
|
|
1986
|
+
- 查询复杂度高时可引入规约模式(Specification)
|
|
1987
|
+
|
|
1988
|
+
### 2. 工作单元模式 (Unit of Work)
|
|
1989
|
+
|
|
1990
|
+
**问题描述**: 跟踪业务事务中所有受影响的对象,协调变更的持久化。
|
|
1991
|
+
|
|
1992
|
+
**Python实现**:
|
|
1993
|
+
|
|
1994
|
+
```python
|
|
1995
|
+
from __future__ import annotations
|
|
1996
|
+
from contextlib import contextmanager
|
|
1997
|
+
from typing import Generator
|
|
1998
|
+
|
|
1999
|
+
class UnitOfWork:
|
|
2000
|
+
"""工作单元:收集变更,统一提交或回滚"""
|
|
2001
|
+
def __init__(self, session_factory) -> None:
|
|
2002
|
+
self._session_factory = session_factory
|
|
2003
|
+
self._session = None
|
|
2004
|
+
self._new: list = []
|
|
2005
|
+
self._dirty: list = []
|
|
2006
|
+
self._removed: list = []
|
|
2007
|
+
|
|
2008
|
+
def __enter__(self) -> "UnitOfWork":
|
|
2009
|
+
self._session = self._session_factory()
|
|
2010
|
+
self._new.clear()
|
|
2011
|
+
self._dirty.clear()
|
|
2012
|
+
self._removed.clear()
|
|
2013
|
+
return self
|
|
2014
|
+
|
|
2015
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
2016
|
+
if exc_type:
|
|
2017
|
+
self.rollback()
|
|
2018
|
+
self._session = None
|
|
2019
|
+
return False
|
|
2020
|
+
|
|
2021
|
+
def register_new(self, entity) -> None:
|
|
2022
|
+
self._new.append(entity)
|
|
2023
|
+
|
|
2024
|
+
def register_dirty(self, entity) -> None:
|
|
2025
|
+
if entity not in self._dirty:
|
|
2026
|
+
self._dirty.append(entity)
|
|
2027
|
+
|
|
2028
|
+
def register_removed(self, entity) -> None:
|
|
2029
|
+
self._removed.append(entity)
|
|
2030
|
+
|
|
2031
|
+
def commit(self) -> None:
|
|
2032
|
+
for entity in self._new:
|
|
2033
|
+
self._session.add(entity)
|
|
2034
|
+
for entity in self._dirty:
|
|
2035
|
+
self._session.merge(entity)
|
|
2036
|
+
for entity in self._removed:
|
|
2037
|
+
self._session.delete(entity)
|
|
2038
|
+
self._session.commit()
|
|
2039
|
+
self._new.clear()
|
|
2040
|
+
self._dirty.clear()
|
|
2041
|
+
self._removed.clear()
|
|
2042
|
+
|
|
2043
|
+
def rollback(self) -> None:
|
|
2044
|
+
if self._session:
|
|
2045
|
+
self._session.rollback()
|
|
2046
|
+
self._new.clear()
|
|
2047
|
+
self._dirty.clear()
|
|
2048
|
+
self._removed.clear()
|
|
2049
|
+
|
|
2050
|
+
# 使用
|
|
2051
|
+
# with UnitOfWork(session_factory) as uow:
|
|
2052
|
+
# order = uow.orders.get("ORD-001")
|
|
2053
|
+
# order.status = "shipped"
|
|
2054
|
+
# uow.register_dirty(order)
|
|
2055
|
+
# uow.commit()
|
|
2056
|
+
```
|
|
2057
|
+
|
|
2058
|
+
**使用场景**: 数据库事务管理、批量操作原子性、跨仓储一致性。
|
|
2059
|
+
|
|
2060
|
+
**注意事项**:
|
|
2061
|
+
- SQLAlchemy的`Session`本身就是工作单元的实现
|
|
2062
|
+
- 与仓储模式配合使用,仓储在UoW内操作
|
|
2063
|
+
- 保持事务粒度尽量小
|
|
2064
|
+
|
|
2065
|
+
### 3. CQRS模式 (Command Query Responsibility Segregation)
|
|
2066
|
+
|
|
2067
|
+
**问题描述**: 将读操作(查询)和写操作(命令)分离为不同的模型。
|
|
2068
|
+
|
|
2069
|
+
**Python实现**:
|
|
2070
|
+
|
|
2071
|
+
```python
|
|
2072
|
+
from __future__ import annotations
|
|
2073
|
+
from dataclasses import dataclass
|
|
2074
|
+
from typing import Protocol, Any
|
|
2075
|
+
|
|
2076
|
+
# 命令侧(写)
|
|
2077
|
+
@dataclass
|
|
2078
|
+
class CreateOrderCommand:
|
|
2079
|
+
customer_id: str
|
|
2080
|
+
items: list[dict]
|
|
2081
|
+
|
|
2082
|
+
@dataclass
|
|
2083
|
+
class CancelOrderCommand:
|
|
2084
|
+
order_id: str
|
|
2085
|
+
reason: str
|
|
2086
|
+
|
|
2087
|
+
class CommandHandler(Protocol):
|
|
2088
|
+
def handle(self, command: Any) -> None: ...
|
|
2089
|
+
|
|
2090
|
+
class CreateOrderHandler:
|
|
2091
|
+
def __init__(self, repo, event_bus) -> None:
|
|
2092
|
+
self.repo = repo
|
|
2093
|
+
self.event_bus = event_bus
|
|
2094
|
+
|
|
2095
|
+
def handle(self, command: CreateOrderCommand) -> None:
|
|
2096
|
+
order_id = f"ORD-{hash(command.customer_id) % 100000}"
|
|
2097
|
+
order = {
|
|
2098
|
+
"id": order_id,
|
|
2099
|
+
"customer_id": command.customer_id,
|
|
2100
|
+
"items": command.items,
|
|
2101
|
+
"status": "pending",
|
|
2102
|
+
}
|
|
2103
|
+
self.repo.add(order)
|
|
2104
|
+
self.event_bus.publish("order.created", {"order_id": order_id})
|
|
2105
|
+
|
|
2106
|
+
# 查询侧(读)
|
|
2107
|
+
@dataclass
|
|
2108
|
+
class OrderSummaryQuery:
|
|
2109
|
+
customer_id: str
|
|
2110
|
+
status: str | None = None
|
|
2111
|
+
|
|
2112
|
+
@dataclass
|
|
2113
|
+
class OrderSummary:
|
|
2114
|
+
order_id: str
|
|
2115
|
+
total: float
|
|
2116
|
+
status: str
|
|
2117
|
+
item_count: int
|
|
2118
|
+
|
|
2119
|
+
class OrderQueryService:
|
|
2120
|
+
"""查询服务:专为读优化的独立模型"""
|
|
2121
|
+
def __init__(self, read_db) -> None:
|
|
2122
|
+
self._db = read_db
|
|
2123
|
+
|
|
2124
|
+
def get_order_summaries(self, query: OrderSummaryQuery) -> list[OrderSummary]:
|
|
2125
|
+
# 读模型可以是反范式化的视图、缓存或搜索索引
|
|
2126
|
+
results = self._db.execute(
|
|
2127
|
+
"SELECT order_id, total, status, item_count FROM order_summaries "
|
|
2128
|
+
"WHERE customer_id = :cid",
|
|
2129
|
+
{"cid": query.customer_id},
|
|
2130
|
+
)
|
|
2131
|
+
return [
|
|
2132
|
+
OrderSummary(
|
|
2133
|
+
order_id=r.order_id,
|
|
2134
|
+
total=r.total,
|
|
2135
|
+
status=r.status,
|
|
2136
|
+
item_count=r.item_count,
|
|
2137
|
+
)
|
|
2138
|
+
for r in results
|
|
2139
|
+
]
|
|
2140
|
+
|
|
2141
|
+
# 命令总线
|
|
2142
|
+
class CommandBus:
|
|
2143
|
+
def __init__(self) -> None:
|
|
2144
|
+
self._handlers: dict[type, CommandHandler] = {}
|
|
2145
|
+
|
|
2146
|
+
def register(self, command_type: type, handler: CommandHandler) -> None:
|
|
2147
|
+
self._handlers[command_type] = handler
|
|
2148
|
+
|
|
2149
|
+
def dispatch(self, command: Any) -> None:
|
|
2150
|
+
handler = self._handlers.get(type(command))
|
|
2151
|
+
if handler is None:
|
|
2152
|
+
raise ValueError(f"No handler for {type(command).__name__}")
|
|
2153
|
+
handler.handle(command)
|
|
2154
|
+
```
|
|
2155
|
+
|
|
2156
|
+
**使用场景**: 高读写比系统、需要独立扩展读写的系统、事件驱动架构。
|
|
2157
|
+
|
|
2158
|
+
**注意事项**:
|
|
2159
|
+
- CQRS增加了系统复杂度——只在读写模型差异大时使用
|
|
2160
|
+
- 通常与事件溯源配合,读模型通过事件投影生成
|
|
2161
|
+
- 读写模型最终一致即可,不要追求强一致
|
|
2162
|
+
|
|
2163
|
+
### 4. 事件溯源模式 (Event Sourcing)
|
|
2164
|
+
|
|
2165
|
+
**问题描述**: 不存储当前状态,而是存储导致当前状态的所有事件序列。
|
|
2166
|
+
|
|
2167
|
+
**Python实现**:
|
|
2168
|
+
|
|
2169
|
+
```python
|
|
2170
|
+
from __future__ import annotations
|
|
2171
|
+
from dataclasses import dataclass, field
|
|
2172
|
+
from datetime import datetime
|
|
2173
|
+
from typing import Any
|
|
2174
|
+
|
|
2175
|
+
@dataclass(frozen=True)
|
|
2176
|
+
class DomainEvent:
|
|
2177
|
+
event_type: str
|
|
2178
|
+
aggregate_id: str
|
|
2179
|
+
data: dict[str, Any]
|
|
2180
|
+
timestamp: datetime = field(default_factory=datetime.utcnow)
|
|
2181
|
+
version: int = 0
|
|
2182
|
+
|
|
2183
|
+
class EventStore:
|
|
2184
|
+
"""事件存储:追加写入,按聚合ID查询"""
|
|
2185
|
+
def __init__(self) -> None:
|
|
2186
|
+
self._events: list[DomainEvent] = []
|
|
2187
|
+
|
|
2188
|
+
def append(self, event: DomainEvent) -> None:
|
|
2189
|
+
self._events.append(event)
|
|
2190
|
+
|
|
2191
|
+
def get_events(self, aggregate_id: str) -> list[DomainEvent]:
|
|
2192
|
+
return [e for e in self._events if e.aggregate_id == aggregate_id]
|
|
2193
|
+
|
|
2194
|
+
class BankAccount:
|
|
2195
|
+
"""银行账户聚合:通过事件重建状态"""
|
|
2196
|
+
def __init__(self, account_id: str) -> None:
|
|
2197
|
+
self.account_id = account_id
|
|
2198
|
+
self.balance: float = 0.0
|
|
2199
|
+
self.is_active: bool = False
|
|
2200
|
+
self._pending_events: list[DomainEvent] = []
|
|
2201
|
+
self._version: int = 0
|
|
2202
|
+
|
|
2203
|
+
def apply_event(self, event: DomainEvent) -> None:
|
|
2204
|
+
handler = getattr(self, f"_on_{event.event_type}", None)
|
|
2205
|
+
if handler:
|
|
2206
|
+
handler(event.data)
|
|
2207
|
+
self._version = event.version
|
|
2208
|
+
|
|
2209
|
+
def _on_account_opened(self, data: dict) -> None:
|
|
2210
|
+
self.is_active = True
|
|
2211
|
+
self.balance = data.get("initial_deposit", 0.0)
|
|
2212
|
+
|
|
2213
|
+
def _on_money_deposited(self, data: dict) -> None:
|
|
2214
|
+
self.balance += data["amount"]
|
|
2215
|
+
|
|
2216
|
+
def _on_money_withdrawn(self, data: dict) -> None:
|
|
2217
|
+
self.balance -= data["amount"]
|
|
2218
|
+
|
|
2219
|
+
def _on_account_closed(self, data: dict) -> None:
|
|
2220
|
+
self.is_active = False
|
|
2221
|
+
|
|
2222
|
+
# 命令方法:产生事件
|
|
2223
|
+
def open(self, initial_deposit: float) -> None:
|
|
2224
|
+
if self.is_active:
|
|
2225
|
+
raise ValueError("Account already open")
|
|
2226
|
+
self._raise_event("account_opened", {"initial_deposit": initial_deposit})
|
|
2227
|
+
|
|
2228
|
+
def deposit(self, amount: float) -> None:
|
|
2229
|
+
if not self.is_active:
|
|
2230
|
+
raise ValueError("Account is closed")
|
|
2231
|
+
if amount <= 0:
|
|
2232
|
+
raise ValueError("Amount must be positive")
|
|
2233
|
+
self._raise_event("money_deposited", {"amount": amount})
|
|
2234
|
+
|
|
2235
|
+
def withdraw(self, amount: float) -> None:
|
|
2236
|
+
if not self.is_active:
|
|
2237
|
+
raise ValueError("Account is closed")
|
|
2238
|
+
if amount > self.balance:
|
|
2239
|
+
raise ValueError("Insufficient funds")
|
|
2240
|
+
self._raise_event("money_withdrawn", {"amount": amount})
|
|
2241
|
+
|
|
2242
|
+
def _raise_event(self, event_type: str, data: dict) -> None:
|
|
2243
|
+
event = DomainEvent(
|
|
2244
|
+
event_type=event_type,
|
|
2245
|
+
aggregate_id=self.account_id,
|
|
2246
|
+
data=data,
|
|
2247
|
+
version=self._version + 1,
|
|
2248
|
+
)
|
|
2249
|
+
self.apply_event(event)
|
|
2250
|
+
self._pending_events.append(event)
|
|
2251
|
+
|
|
2252
|
+
def flush_events(self) -> list[DomainEvent]:
|
|
2253
|
+
events = self._pending_events[:]
|
|
2254
|
+
self._pending_events.clear()
|
|
2255
|
+
return events
|
|
2256
|
+
|
|
2257
|
+
@classmethod
|
|
2258
|
+
def from_events(cls, account_id: str, events: list[DomainEvent]) -> BankAccount:
|
|
2259
|
+
"""从事件流重建聚合状态"""
|
|
2260
|
+
account = cls(account_id)
|
|
2261
|
+
for event in events:
|
|
2262
|
+
account.apply_event(event)
|
|
2263
|
+
return account
|
|
2264
|
+
|
|
2265
|
+
# 使用
|
|
2266
|
+
store = EventStore()
|
|
2267
|
+
account = BankAccount("ACC-001")
|
|
2268
|
+
account.open(1000.0)
|
|
2269
|
+
account.deposit(500.0)
|
|
2270
|
+
account.withdraw(200.0)
|
|
2271
|
+
|
|
2272
|
+
# 持久化事件
|
|
2273
|
+
for event in account.flush_events():
|
|
2274
|
+
store.append(event)
|
|
2275
|
+
|
|
2276
|
+
# 从事件重建状态
|
|
2277
|
+
rebuilt = BankAccount.from_events("ACC-001", store.get_events("ACC-001"))
|
|
2278
|
+
print(rebuilt.balance) # 1300.0
|
|
2279
|
+
```
|
|
2280
|
+
|
|
2281
|
+
**使用场景**: 金融系统、审计追踪、时间旅行调试、分布式系统状态同步。
|
|
2282
|
+
|
|
2283
|
+
**注意事项**:
|
|
2284
|
+
- 事件一旦持久化就不可变——修正错误通过补偿事件
|
|
2285
|
+
- 事件流过长时使用快照优化重建性能
|
|
2286
|
+
- 事件schema的演化需要版本管理
|
|
2287
|
+
|
|
2288
|
+
### 5. 领域驱动设计在Python中的实现 (DDD)
|
|
2289
|
+
|
|
2290
|
+
**问题描述**: 将复杂业务逻辑组织为领域模型,使代码结构反映业务概念。
|
|
2291
|
+
|
|
2292
|
+
**Python实现**:
|
|
2293
|
+
|
|
2294
|
+
```python
|
|
2295
|
+
from __future__ import annotations
|
|
2296
|
+
from dataclasses import dataclass, field
|
|
2297
|
+
from datetime import datetime
|
|
2298
|
+
from typing import NewType
|
|
2299
|
+
from enum import Enum, auto
|
|
2300
|
+
|
|
2301
|
+
# 值对象
|
|
2302
|
+
OrderId = NewType("OrderId", str)
|
|
2303
|
+
CustomerId = NewType("CustomerId", str)
|
|
2304
|
+
|
|
2305
|
+
@dataclass(frozen=True)
|
|
2306
|
+
class Money:
|
|
2307
|
+
"""值对象:不可变,通过值比较"""
|
|
2308
|
+
amount: float
|
|
2309
|
+
currency: str = "CNY"
|
|
2310
|
+
|
|
2311
|
+
def __add__(self, other: Money) -> Money:
|
|
2312
|
+
if self.currency != other.currency:
|
|
2313
|
+
raise ValueError(f"Cannot add {self.currency} and {other.currency}")
|
|
2314
|
+
return Money(self.amount + other.amount, self.currency)
|
|
2315
|
+
|
|
2316
|
+
def __mul__(self, factor: int | float) -> Money:
|
|
2317
|
+
return Money(round(self.amount * factor, 2), self.currency)
|
|
2318
|
+
|
|
2319
|
+
class OrderStatus(Enum):
|
|
2320
|
+
DRAFT = auto()
|
|
2321
|
+
CONFIRMED = auto()
|
|
2322
|
+
PAID = auto()
|
|
2323
|
+
SHIPPED = auto()
|
|
2324
|
+
COMPLETED = auto()
|
|
2325
|
+
CANCELLED = auto()
|
|
2326
|
+
|
|
2327
|
+
# 实体
|
|
2328
|
+
@dataclass
|
|
2329
|
+
class OrderItem:
|
|
2330
|
+
product_id: str
|
|
2331
|
+
product_name: str
|
|
2332
|
+
unit_price: Money
|
|
2333
|
+
quantity: int
|
|
2334
|
+
|
|
2335
|
+
@property
|
|
2336
|
+
def subtotal(self) -> Money:
|
|
2337
|
+
return self.unit_price * self.quantity
|
|
2338
|
+
|
|
2339
|
+
# 聚合根
|
|
2340
|
+
@dataclass
|
|
2341
|
+
class Order:
|
|
2342
|
+
"""订单聚合根:封装所有业务不变量"""
|
|
2343
|
+
id: OrderId
|
|
2344
|
+
customer_id: CustomerId
|
|
2345
|
+
items: list[OrderItem] = field(default_factory=list)
|
|
2346
|
+
status: OrderStatus = OrderStatus.DRAFT
|
|
2347
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
2348
|
+
_events: list[dict] = field(default_factory=list, repr=False)
|
|
2349
|
+
|
|
2350
|
+
@property
|
|
2351
|
+
def total(self) -> Money:
|
|
2352
|
+
if not self.items:
|
|
2353
|
+
return Money(0.0)
|
|
2354
|
+
result = Money(0.0)
|
|
2355
|
+
for item in self.items:
|
|
2356
|
+
result = result + item.subtotal
|
|
2357
|
+
return result
|
|
2358
|
+
|
|
2359
|
+
def add_item(self, product_id: str, name: str, price: Money, qty: int) -> None:
|
|
2360
|
+
if self.status != OrderStatus.DRAFT:
|
|
2361
|
+
raise ValueError("Can only add items to draft orders")
|
|
2362
|
+
if qty <= 0:
|
|
2363
|
+
raise ValueError("Quantity must be positive")
|
|
2364
|
+
self.items.append(OrderItem(product_id, name, price, qty))
|
|
2365
|
+
|
|
2366
|
+
def confirm(self) -> None:
|
|
2367
|
+
if self.status != OrderStatus.DRAFT:
|
|
2368
|
+
raise ValueError(f"Cannot confirm order in {self.status.name} status")
|
|
2369
|
+
if not self.items:
|
|
2370
|
+
raise ValueError("Cannot confirm empty order")
|
|
2371
|
+
self.status = OrderStatus.CONFIRMED
|
|
2372
|
+
self._events.append({"type": "order.confirmed", "order_id": self.id})
|
|
2373
|
+
|
|
2374
|
+
def cancel(self, reason: str) -> None:
|
|
2375
|
+
if self.status in {OrderStatus.SHIPPED, OrderStatus.COMPLETED}:
|
|
2376
|
+
raise ValueError(f"Cannot cancel order in {self.status.name} status")
|
|
2377
|
+
self.status = OrderStatus.CANCELLED
|
|
2378
|
+
self._events.append({
|
|
2379
|
+
"type": "order.cancelled",
|
|
2380
|
+
"order_id": self.id,
|
|
2381
|
+
"reason": reason,
|
|
2382
|
+
})
|
|
2383
|
+
|
|
2384
|
+
def collect_events(self) -> list[dict]:
|
|
2385
|
+
events = self._events[:]
|
|
2386
|
+
self._events.clear()
|
|
2387
|
+
return events
|
|
2388
|
+
|
|
2389
|
+
# 领域服务
|
|
2390
|
+
class PricingService:
|
|
2391
|
+
"""领域服务:跨聚合的业务逻辑"""
|
|
2392
|
+
def calculate_discount(self, order: Order, customer_tier: str) -> Money:
|
|
2393
|
+
base = order.total
|
|
2394
|
+
discount_rate = {"bronze": 0.0, "silver": 0.05, "gold": 0.10, "platinum": 0.15}
|
|
2395
|
+
rate = discount_rate.get(customer_tier, 0.0)
|
|
2396
|
+
return Money(round(base.amount * rate, 2), base.currency)
|
|
2397
|
+
|
|
2398
|
+
# 使用
|
|
2399
|
+
order = Order(id=OrderId("ORD-001"), customer_id=CustomerId("CUST-042"))
|
|
2400
|
+
order.add_item("PROD-1", "Widget", Money(29.90), 3)
|
|
2401
|
+
order.add_item("PROD-2", "Gadget", Money(99.00), 1)
|
|
2402
|
+
print(order.total) # Money(amount=188.7, currency='CNY')
|
|
2403
|
+
order.confirm()
|
|
2404
|
+
events = order.collect_events() # [{'type': 'order.confirmed', ...}]
|
|
2405
|
+
```
|
|
2406
|
+
|
|
2407
|
+
**使用场景**: 复杂业务系统(电商、金融、物流)、需要长期维护的核心域。
|
|
2408
|
+
|
|
2409
|
+
**注意事项**:
|
|
2410
|
+
- 值对象必须不可变(`frozen=True`)
|
|
2411
|
+
- 聚合根是事务一致性的边界——一个事务只修改一个聚合
|
|
2412
|
+
- 跨聚合的业务规则放在领域服务中
|
|
2413
|
+
- 不要对所有代码都用DDD——只对核心复杂域使用
|
|
2414
|
+
|
|
2415
|
+
---
|
|
2416
|
+
|
|
2417
|
+
## 反模式
|
|
2418
|
+
|
|
2419
|
+
### 1. God Object(上帝对象)
|
|
2420
|
+
|
|
2421
|
+
**问题描述**: 一个类承担了过多职责,知道太多、做太多。
|
|
2422
|
+
|
|
2423
|
+
**识别信号**:
|
|
2424
|
+
- 文件超过1000行
|
|
2425
|
+
- 类有20+个方法
|
|
2426
|
+
- 构造函数注入10+个依赖
|
|
2427
|
+
- 修改任何功能都要改这个类
|
|
2428
|
+
|
|
2429
|
+
**修复方案**: 按职责拆分为多个协作类,使用外观模式提供统一入口。
|
|
2430
|
+
|
|
2431
|
+
```python
|
|
2432
|
+
# 反模式
|
|
2433
|
+
class OrderManager:
|
|
2434
|
+
def create_order(self): ...
|
|
2435
|
+
def process_payment(self): ...
|
|
2436
|
+
def send_notification(self): ...
|
|
2437
|
+
def generate_invoice(self): ...
|
|
2438
|
+
def update_inventory(self): ...
|
|
2439
|
+
def calculate_shipping(self): ...
|
|
2440
|
+
def apply_discount(self): ...
|
|
2441
|
+
def handle_refund(self): ...
|
|
2442
|
+
# ... 50+ methods
|
|
2443
|
+
|
|
2444
|
+
# 正确做法:拆分为独立服务
|
|
2445
|
+
class OrderService:
|
|
2446
|
+
def __init__(self, payment: PaymentService, inventory: InventoryService):
|
|
2447
|
+
self.payment = payment
|
|
2448
|
+
self.inventory = inventory
|
|
2449
|
+
|
|
2450
|
+
def create_order(self, items): ...
|
|
2451
|
+
|
|
2452
|
+
class PaymentService:
|
|
2453
|
+
def process(self, order): ...
|
|
2454
|
+
def refund(self, order): ...
|
|
2455
|
+
|
|
2456
|
+
class InventoryService:
|
|
2457
|
+
def reserve(self, items): ...
|
|
2458
|
+
def release(self, items): ...
|
|
2459
|
+
```
|
|
2460
|
+
|
|
2461
|
+
### 2. Spaghetti Code(意大利面代码)
|
|
2462
|
+
|
|
2463
|
+
**问题描述**: 缺乏清晰结构,控制流复杂交织,难以追踪执行路径。
|
|
2464
|
+
|
|
2465
|
+
**识别信号**:
|
|
2466
|
+
- 函数超过50行
|
|
2467
|
+
- 深层嵌套(if-else超过4层)
|
|
2468
|
+
- 大量全局变量
|
|
2469
|
+
- goto风格的控制流(异常用于流程控制)
|
|
2470
|
+
|
|
2471
|
+
**修复方案**: 提取函数、使用早返回、引入状态模式或策略模式。
|
|
2472
|
+
|
|
2473
|
+
```python
|
|
2474
|
+
# 反模式:深层嵌套
|
|
2475
|
+
def process_order(order):
|
|
2476
|
+
if order:
|
|
2477
|
+
if order.items:
|
|
2478
|
+
if order.customer:
|
|
2479
|
+
if order.customer.is_active:
|
|
2480
|
+
if order.total > 0:
|
|
2481
|
+
# 实际逻辑深埋在这里
|
|
2482
|
+
pass
|
|
2483
|
+
|
|
2484
|
+
# 正确做法:卫语句 + 早返回
|
|
2485
|
+
def process_order(order):
|
|
2486
|
+
if not order:
|
|
2487
|
+
raise ValueError("Order is required")
|
|
2488
|
+
if not order.items:
|
|
2489
|
+
raise ValueError("Order must have items")
|
|
2490
|
+
if not order.customer or not order.customer.is_active:
|
|
2491
|
+
raise ValueError("Active customer required")
|
|
2492
|
+
if order.total <= 0:
|
|
2493
|
+
raise ValueError("Order total must be positive")
|
|
2494
|
+
# 实际逻辑在最外层
|
|
2495
|
+
_execute_order(order)
|
|
2496
|
+
```
|
|
2497
|
+
|
|
2498
|
+
### 3. 过度设计 (Over-Engineering)
|
|
2499
|
+
|
|
2500
|
+
**问题描述**: 为未来可能永远不会出现的需求增加不必要的抽象层。
|
|
2501
|
+
|
|
2502
|
+
**识别信号**:
|
|
2503
|
+
- 只有一个实现的接口/抽象类
|
|
2504
|
+
- 为3个字段创建Builder模式
|
|
2505
|
+
- 未使用的扩展点
|
|
2506
|
+
- "以后可能需要"的抽象
|
|
2507
|
+
|
|
2508
|
+
**修复方案**: 遵循YAGNI(You Ain't Gonna Need It),先实现最简方案,当第三次遇到相似需求时再抽象。
|
|
2509
|
+
|
|
2510
|
+
```python
|
|
2511
|
+
# 过度设计:只有一种通知方式却创建了完整的策略体系
|
|
2512
|
+
class NotificationStrategyFactory(AbstractNotificationFactory):
|
|
2513
|
+
... # 100行只为发一封邮件
|
|
2514
|
+
|
|
2515
|
+
# 正确做法:直接写
|
|
2516
|
+
def send_welcome_email(user_email: str, user_name: str) -> None:
|
|
2517
|
+
# 直接发邮件,等真正需要SMS时再抽象
|
|
2518
|
+
smtp.send(to=user_email, subject="Welcome", body=f"Hi {user_name}")
|
|
2519
|
+
```
|
|
2520
|
+
|
|
2521
|
+
### 4. 过早抽象 (Premature Abstraction)
|
|
2522
|
+
|
|
2523
|
+
**问题描述**: 在只有一个用例时就创建通用框架,导致抽象与实际需求不匹配。
|
|
2524
|
+
|
|
2525
|
+
**修复方案**: Rule of Three——等看到三个相似场景后再提取抽象。
|
|
2526
|
+
|
|
2527
|
+
### 5. Singleton滥用
|
|
2528
|
+
|
|
2529
|
+
**问题描述**: 将单例当作全局变量使用,导致隐式依赖、测试困难、并发问题。
|
|
2530
|
+
|
|
2531
|
+
**识别信号**:
|
|
2532
|
+
- 在函数内部直接调用`XxxManager.instance()`而非通过参数接收
|
|
2533
|
+
- 单例持有可变状态且被多线程访问
|
|
2534
|
+
- 测试时需要"重置"单例状态
|
|
2535
|
+
|
|
2536
|
+
**修复方案**: 用依赖注入替代。将单例降级为"只创建一次"的普通对象,通过参数传递。
|
|
2537
|
+
|
|
2538
|
+
```python
|
|
2539
|
+
# 反模式:到处直接访问单例
|
|
2540
|
+
def process_payment(amount):
|
|
2541
|
+
config = AppConfig.instance() # 隐式依赖
|
|
2542
|
+
db = DatabasePool.instance() # 隐式依赖
|
|
2543
|
+
logger = Logger.instance() # 隐式依赖
|
|
2544
|
+
...
|
|
2545
|
+
|
|
2546
|
+
# 正确做法:显式依赖
|
|
2547
|
+
def process_payment(amount, config, db, logger):
|
|
2548
|
+
...
|
|
2549
|
+
|
|
2550
|
+
# 在组合根(入口点)创建并注入
|
|
2551
|
+
config = AppConfig()
|
|
2552
|
+
db = DatabasePool(config.db_url)
|
|
2553
|
+
logger = Logger(config.log_level)
|
|
2554
|
+
process_payment(100.0, config, db, logger)
|
|
2555
|
+
```
|
|
2556
|
+
|
|
2557
|
+
---
|
|
2558
|
+
|
|
2559
|
+
## 实战案例:用设计模式重构支付系统
|
|
2560
|
+
|
|
2561
|
+
### 重构前:混乱的支付处理
|
|
2562
|
+
|
|
2563
|
+
```python
|
|
2564
|
+
# payment_processor.py - 重构前(典型的God Object + Spaghetti Code)
|
|
2565
|
+
class PaymentProcessor:
|
|
2566
|
+
def __init__(self):
|
|
2567
|
+
self.db = DatabasePool.instance()
|
|
2568
|
+
self.config = AppConfig.instance()
|
|
2569
|
+
self.logger = Logger.instance()
|
|
2570
|
+
|
|
2571
|
+
def process(self, order_id, payment_type, card_number=None,
|
|
2572
|
+
alipay_id=None, wechat_openid=None, amount=None):
|
|
2573
|
+
# 800行方法,处理所有支付类型
|
|
2574
|
+
order = self.db.query(f"SELECT * FROM orders WHERE id = '{order_id}'") # SQL注入
|
|
2575
|
+
if not order:
|
|
2576
|
+
return {"success": False, "error": "Order not found"}
|
|
2577
|
+
|
|
2578
|
+
if payment_type == "credit_card":
|
|
2579
|
+
if not card_number:
|
|
2580
|
+
return {"success": False, "error": "Card number required"}
|
|
2581
|
+
# 100行信用卡处理逻辑...
|
|
2582
|
+
result = self._call_stripe(card_number, amount)
|
|
2583
|
+
if result["status"] == "success":
|
|
2584
|
+
self.db.execute(f"UPDATE orders SET status='paid' WHERE id='{order_id}'")
|
|
2585
|
+
self.db.execute(f"INSERT INTO payments ...")
|
|
2586
|
+
# 发送邮件通知
|
|
2587
|
+
import smtplib
|
|
2588
|
+
server = smtplib.SMTP("smtp.example.com")
|
|
2589
|
+
server.sendmail("noreply@shop.com", order["email"], "Payment received")
|
|
2590
|
+
return {"success": True}
|
|
2591
|
+
else:
|
|
2592
|
+
return {"success": False, "error": result["error"]}
|
|
2593
|
+
elif payment_type == "alipay":
|
|
2594
|
+
# 又是100行支付宝逻辑...
|
|
2595
|
+
pass
|
|
2596
|
+
elif payment_type == "wechat":
|
|
2597
|
+
# 又是100行微信逻辑...
|
|
2598
|
+
pass
|
|
2599
|
+
else:
|
|
2600
|
+
return {"success": False, "error": "Unknown payment type"}
|
|
2601
|
+
```
|
|
2602
|
+
|
|
2603
|
+
### 重构后:清晰的模式应用
|
|
2604
|
+
|
|
2605
|
+
```python
|
|
2606
|
+
# domain/models.py - 值对象和实体
|
|
2607
|
+
from dataclasses import dataclass
|
|
2608
|
+
from enum import Enum, auto
|
|
2609
|
+
|
|
2610
|
+
class PaymentMethod(Enum):
|
|
2611
|
+
CREDIT_CARD = auto()
|
|
2612
|
+
ALIPAY = auto()
|
|
2613
|
+
WECHAT_PAY = auto()
|
|
2614
|
+
|
|
2615
|
+
@dataclass(frozen=True)
|
|
2616
|
+
class Money:
|
|
2617
|
+
amount: float
|
|
2618
|
+
currency: str = "CNY"
|
|
2619
|
+
|
|
2620
|
+
def __post_init__(self):
|
|
2621
|
+
if self.amount < 0:
|
|
2622
|
+
raise ValueError("Amount cannot be negative")
|
|
2623
|
+
|
|
2624
|
+
@dataclass
|
|
2625
|
+
class PaymentResult:
|
|
2626
|
+
success: bool
|
|
2627
|
+
transaction_id: str | None = None
|
|
2628
|
+
error: str | None = None
|
|
2629
|
+
|
|
2630
|
+
|
|
2631
|
+
# domain/gateway.py - 策略模式:支付网关
|
|
2632
|
+
from typing import Protocol
|
|
2633
|
+
|
|
2634
|
+
class PaymentGateway(Protocol):
|
|
2635
|
+
"""支付网关协议——每种支付方式一个实现"""
|
|
2636
|
+
def charge(self, amount: Money, credentials: dict) -> PaymentResult: ...
|
|
2637
|
+
def refund(self, transaction_id: str, amount: Money) -> PaymentResult: ...
|
|
2638
|
+
|
|
2639
|
+
class StripeGateway:
|
|
2640
|
+
def __init__(self, api_key: str) -> None:
|
|
2641
|
+
self._api_key = api_key
|
|
2642
|
+
|
|
2643
|
+
def charge(self, amount: Money, credentials: dict) -> PaymentResult:
|
|
2644
|
+
card = credentials.get("card_number")
|
|
2645
|
+
if not card:
|
|
2646
|
+
return PaymentResult(success=False, error="Card number required")
|
|
2647
|
+
# 调用Stripe API
|
|
2648
|
+
return PaymentResult(success=True, transaction_id="stripe_txn_001")
|
|
2649
|
+
|
|
2650
|
+
def refund(self, transaction_id: str, amount: Money) -> PaymentResult:
|
|
2651
|
+
return PaymentResult(success=True, transaction_id=f"refund_{transaction_id}")
|
|
2652
|
+
|
|
2653
|
+
class AlipayGateway:
|
|
2654
|
+
def __init__(self, app_id: str, private_key: str) -> None:
|
|
2655
|
+
self._app_id = app_id
|
|
2656
|
+
self._private_key = private_key
|
|
2657
|
+
|
|
2658
|
+
def charge(self, amount: Money, credentials: dict) -> PaymentResult:
|
|
2659
|
+
return PaymentResult(success=True, transaction_id="alipay_txn_001")
|
|
2660
|
+
|
|
2661
|
+
def refund(self, transaction_id: str, amount: Money) -> PaymentResult:
|
|
2662
|
+
return PaymentResult(success=True, transaction_id=f"refund_{transaction_id}")
|
|
2663
|
+
|
|
2664
|
+
class WechatPayGateway:
|
|
2665
|
+
def __init__(self, mch_id: str, api_key: str) -> None:
|
|
2666
|
+
self._mch_id = mch_id
|
|
2667
|
+
self._api_key = api_key
|
|
2668
|
+
|
|
2669
|
+
def charge(self, amount: Money, credentials: dict) -> PaymentResult:
|
|
2670
|
+
return PaymentResult(success=True, transaction_id="wechat_txn_001")
|
|
2671
|
+
|
|
2672
|
+
def refund(self, transaction_id: str, amount: Money) -> PaymentResult:
|
|
2673
|
+
return PaymentResult(success=True, transaction_id=f"refund_{transaction_id}")
|
|
2674
|
+
|
|
2675
|
+
|
|
2676
|
+
# domain/repository.py - 仓储模式
|
|
2677
|
+
class OrderRepository(Protocol):
|
|
2678
|
+
def get(self, order_id: str) -> dict | None: ...
|
|
2679
|
+
def update_status(self, order_id: str, status: str) -> None: ...
|
|
2680
|
+
|
|
2681
|
+
class PaymentRepository(Protocol):
|
|
2682
|
+
def save(self, payment: dict) -> None: ...
|
|
2683
|
+
def get_by_order(self, order_id: str) -> dict | None: ...
|
|
2684
|
+
|
|
2685
|
+
|
|
2686
|
+
# domain/notification.py - 观察者模式:支付事件通知
|
|
2687
|
+
from collections import defaultdict
|
|
2688
|
+
from typing import Callable, Any
|
|
2689
|
+
|
|
2690
|
+
class PaymentEventBus:
|
|
2691
|
+
def __init__(self) -> None:
|
|
2692
|
+
self._handlers: dict[str, list[Callable]] = defaultdict(list)
|
|
2693
|
+
|
|
2694
|
+
def subscribe(self, event: str, handler: Callable) -> None:
|
|
2695
|
+
self._handlers[event].append(handler)
|
|
2696
|
+
|
|
2697
|
+
def publish(self, event: str, data: dict[str, Any]) -> None:
|
|
2698
|
+
for handler in self._handlers.get(event, []):
|
|
2699
|
+
try:
|
|
2700
|
+
handler(data)
|
|
2701
|
+
except Exception as e:
|
|
2702
|
+
# 通知失败不应影响支付流程
|
|
2703
|
+
print(f"Handler error: {e}")
|
|
2704
|
+
|
|
2705
|
+
def email_notification_handler(data: dict) -> None:
|
|
2706
|
+
print(f"Email: Payment {data['transaction_id']} for order {data['order_id']}")
|
|
2707
|
+
|
|
2708
|
+
def inventory_update_handler(data: dict) -> None:
|
|
2709
|
+
print(f"Inventory: Release reserved items for order {data['order_id']}")
|
|
2710
|
+
|
|
2711
|
+
def audit_log_handler(data: dict) -> None:
|
|
2712
|
+
print(f"Audit: Payment event recorded for order {data['order_id']}")
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
# application/service.py - 外观模式:统一支付服务入口
|
|
2716
|
+
class PaymentService:
|
|
2717
|
+
"""支付服务:协调网关、仓储和事件"""
|
|
2718
|
+
def __init__(
|
|
2719
|
+
self,
|
|
2720
|
+
gateways: dict[PaymentMethod, PaymentGateway],
|
|
2721
|
+
order_repo: OrderRepository,
|
|
2722
|
+
payment_repo: PaymentRepository,
|
|
2723
|
+
event_bus: PaymentEventBus,
|
|
2724
|
+
) -> None:
|
|
2725
|
+
self._gateways = gateways
|
|
2726
|
+
self._orders = order_repo
|
|
2727
|
+
self._payments = payment_repo
|
|
2728
|
+
self._events = event_bus
|
|
2729
|
+
|
|
2730
|
+
def process_payment(
|
|
2731
|
+
self,
|
|
2732
|
+
order_id: str,
|
|
2733
|
+
method: PaymentMethod,
|
|
2734
|
+
credentials: dict,
|
|
2735
|
+
) -> PaymentResult:
|
|
2736
|
+
# 1. 查找订单
|
|
2737
|
+
order = self._orders.get(order_id)
|
|
2738
|
+
if order is None:
|
|
2739
|
+
return PaymentResult(success=False, error="Order not found")
|
|
2740
|
+
|
|
2741
|
+
# 2. 选择支付网关(策略模式)
|
|
2742
|
+
gateway = self._gateways.get(method)
|
|
2743
|
+
if gateway is None:
|
|
2744
|
+
return PaymentResult(success=False, error=f"Unsupported: {method.name}")
|
|
2745
|
+
|
|
2746
|
+
# 3. 执行支付
|
|
2747
|
+
amount = Money(order["total"])
|
|
2748
|
+
result = gateway.charge(amount, credentials)
|
|
2749
|
+
|
|
2750
|
+
if not result.success:
|
|
2751
|
+
return result
|
|
2752
|
+
|
|
2753
|
+
# 4. 持久化(仓储模式)
|
|
2754
|
+
self._orders.update_status(order_id, "paid")
|
|
2755
|
+
self._payments.save({
|
|
2756
|
+
"order_id": order_id,
|
|
2757
|
+
"transaction_id": result.transaction_id,
|
|
2758
|
+
"amount": amount.amount,
|
|
2759
|
+
"method": method.name,
|
|
2760
|
+
})
|
|
2761
|
+
|
|
2762
|
+
# 5. 发布事件(观察者模式)
|
|
2763
|
+
self._events.publish("payment.completed", {
|
|
2764
|
+
"order_id": order_id,
|
|
2765
|
+
"transaction_id": result.transaction_id,
|
|
2766
|
+
"amount": amount.amount,
|
|
2767
|
+
})
|
|
2768
|
+
|
|
2769
|
+
return result
|
|
2770
|
+
|
|
2771
|
+
|
|
2772
|
+
# composition_root.py - 组合根:在入口点组装所有依赖
|
|
2773
|
+
def create_payment_service(config: dict) -> PaymentService:
|
|
2774
|
+
"""依赖注入:在应用入口组装整个对象图"""
|
|
2775
|
+
gateways = {
|
|
2776
|
+
PaymentMethod.CREDIT_CARD: StripeGateway(config["stripe_key"]),
|
|
2777
|
+
PaymentMethod.ALIPAY: AlipayGateway(config["alipay_app_id"], config["alipay_key"]),
|
|
2778
|
+
PaymentMethod.WECHAT_PAY: WechatPayGateway(config["wechat_mch_id"], config["wechat_key"]),
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
event_bus = PaymentEventBus()
|
|
2782
|
+
event_bus.subscribe("payment.completed", email_notification_handler)
|
|
2783
|
+
event_bus.subscribe("payment.completed", inventory_update_handler)
|
|
2784
|
+
event_bus.subscribe("payment.completed", audit_log_handler)
|
|
2785
|
+
|
|
2786
|
+
# order_repo和payment_repo从实际数据库创建
|
|
2787
|
+
# 此处省略,实际使用SQLOrderRepository等
|
|
2788
|
+
|
|
2789
|
+
return PaymentService(
|
|
2790
|
+
gateways=gateways,
|
|
2791
|
+
order_repo=order_repo,
|
|
2792
|
+
payment_repo=payment_repo,
|
|
2793
|
+
event_bus=event_bus,
|
|
2794
|
+
)
|
|
2795
|
+
```
|
|
2796
|
+
|
|
2797
|
+
### 重构收益总结
|
|
2798
|
+
|
|
2799
|
+
| 维度 | 重构前 | 重构后 |
|
|
2800
|
+
|------|--------|--------|
|
|
2801
|
+
| 可测试性 | 无法单测(依赖全局单例) | 每个组件独立可测 |
|
|
2802
|
+
| 扩展性 | 添加支付方式需修改核心类 | 新增Gateway实现即可 |
|
|
2803
|
+
| 安全性 | SQL注入、硬编码凭证 | 参数化查询、配置注入 |
|
|
2804
|
+
| 职责清晰度 | 单文件800行 | 每个模块不超过100行 |
|
|
2805
|
+
| 通知扩展 | 硬编码邮件发送 | 订阅事件即可添加通知渠道 |
|
|
2806
|
+
| 应用模式 | 无 | 策略、仓储、观察者、外观、DI |
|
|
2807
|
+
|
|
2808
|
+
---
|
|
2809
|
+
|
|
2810
|
+
## Agent Checklist
|
|
2811
|
+
|
|
2812
|
+
在代码审查和架构设计中,使用以下检查清单评估设计模式的应用质量:
|
|
2813
|
+
|
|
2814
|
+
### 创建型模式检查
|
|
2815
|
+
- [ ] 工厂方法是否使用字典注册表而非冗长的if-elif链
|
|
2816
|
+
- [ ] 单例实现是否优先使用模块级变量
|
|
2817
|
+
- [ ] 单例是否可以被依赖注入替代以提高可测试性
|
|
2818
|
+
- [ ] Builder是否在构造函数参数超过5个或有构造验证时才使用
|
|
2819
|
+
- [ ] 原型模式是否正确使用了`copy.deepcopy`处理可变嵌套对象
|
|
2820
|
+
|
|
2821
|
+
### 结构型模式检查
|
|
2822
|
+
- [ ] 装饰器是否使用了`@functools.wraps`保留元信息
|
|
2823
|
+
- [ ] 装饰器堆叠顺序是否正确(最内层先执行)
|
|
2824
|
+
- [ ] 适配器是否使用组合而非继承
|
|
2825
|
+
- [ ] 外观是否只封装常见工作流,而非试图代替子系统全部功能
|
|
2826
|
+
- [ ] 代理与装饰器是否正确区分(控制访问 vs 增强功能)
|
|
2827
|
+
|
|
2828
|
+
### 行为型模式检查
|
|
2829
|
+
- [ ] 策略模式是否优先使用函数/callable而非单方法类
|
|
2830
|
+
- [ ] 观察者/事件总线是否有防止内存泄漏的措施(取消订阅、weakref)
|
|
2831
|
+
- [ ] 命令模式的undo是否捕获了执行时的完整状态
|
|
2832
|
+
- [ ] 状态机的转换是否显式定义且不允许非法跳跃
|
|
2833
|
+
- [ ] 责任链中的闭包是否正确绑定了变量(使用默认参数)
|
|
2834
|
+
|
|
2835
|
+
### Python特有模式检查
|
|
2836
|
+
- [ ] Mixin类是否遵守了"不独立实例化"原则
|
|
2837
|
+
- [ ] 描述符是否使用了`__set_name__`自动获取属性名
|
|
2838
|
+
- [ ] 元类是否可以用`__init_subclass__`替代
|
|
2839
|
+
- [ ] 上下文管理器的`__exit__`是否正确处理了异常(不意外吞掉)
|
|
2840
|
+
- [ ] 异步代码是否避免了在async函数中调用阻塞IO
|
|
2841
|
+
|
|
2842
|
+
### 架构模式检查
|
|
2843
|
+
- [ ] 仓储接口是否面向领域语言而非SQL语义
|
|
2844
|
+
- [ ] 工作单元是否在事务边界内正确管理了提交和回滚
|
|
2845
|
+
- [ ] CQRS是否只在读写模型确实不同时才使用
|
|
2846
|
+
- [ ] 事件溯源的事件schema是否有版本管理
|
|
2847
|
+
- [ ] DDD值对象是否不可变(`frozen=True`)
|
|
2848
|
+
|
|
2849
|
+
### 反模式检查
|
|
2850
|
+
- [ ] 是否存在超过500行的单个类文件(God Object信号)
|
|
2851
|
+
- [ ] 是否存在超过4层的if-else嵌套(Spaghetti信号)
|
|
2852
|
+
- [ ] 是否存在只有一个实现的抽象接口(过度设计信号)
|
|
2853
|
+
- [ ] 是否存在函数内部直接调用`.instance()`的单例访问(隐式依赖信号)
|
|
2854
|
+
- [ ] 抽象是否在看到至少三个相似场景后才引入(Rule of Three)
|