AI generates working code by default and clean code by instruction. Learn how senior engineers use AI to enforce SOLID principles, implement GoF patterns correctly, and refactor legacy code into maintainable architecture.
Without explicit architectural constraints, AI produces code that works today and becomes technical debt tomorrow. Our AI coding best practices guide covers the foundational workflows. Here is the difference.
Each pattern solves a specific structural problem. The key to using AI effectively is describing the problem, not the pattern name. For applying patterns in legacy systems, see our AI refactoring guide.
The Strategy pattern replaces switch statements and if-else chains with polymorphism. Instead of a PaymentProcessor class with a method that checks "if Stripe, do this; if PayPal, do that," you create an interface and separate implementations. The calling code depends only on the interface.
To get AI to implement this correctly, describe the business problem: "I have three payment methods that share the same interface but have different implementations. The calling code should not know which processor is active." AI produces the interface, the concrete strategies, and the dependency injection configuration that wires the correct strategy at runtime.
Adding a new payment method becomes a single new class that implements the interface. No existing code is modified, satisfying the Open-Closed Principle. AI can also generate the unit tests for each strategy and the integration test that verifies the correct strategy is resolved.
Use factories when object creation involves conditional logic, configuration, or complex initialization. AI detects factory candidates by finding repeated new ClassName() calls surrounded by conditional logic. If your codebase has switch statements that create different notification channels based on user preferences, that is a factory waiting to be extracted.
AI generates the factory class with a create() method that returns the correct implementation based on input parameters. It registers the factory in your dependency injection container and generates tests that verify each creation path. For Laravel, it integrates with the service container using contextual binding. For TypeScript, it uses a factory function with discriminated union types.
The Observer pattern decouples the code that triggers an action from the code that responds to it. When an order is placed, the inventory system, email system, and analytics system all need to react. Without the Observer pattern, the order placement code directly calls all three, creating tight coupling.
AI generates the event dispatcher, typed event classes with the data each subscriber needs, and the listener implementations. In Laravel, this maps to Event and Listener classes registered in the EventServiceProvider. In Node.js, AI uses EventEmitter or a dedicated pub/sub library. Adding a new subscriber means adding a new listener class without modifying the order placement code.
Your core business logic should never depend directly on third-party APIs. The Adapter pattern creates a layer between your application and external services. If Stripe changes their API, you update one adapter class. If you switch from SendGrid to Postmark, you write a new adapter that implements the same interface.
AI generates adapter classes that translate your application's interface into the third-party API's format. It includes error mapping (converting Stripe's error codes into your application's error types), response normalization, and retry logic. This pattern is essential for AI-assisted backend development because it creates a stable boundary that AI can work within without needing to understand every external API's quirks.
SOLID principles are not abstract guidelines. They are concrete rules that AI can enforce during code generation. Define them in your Cursor rules file or Claude project instructions: every class has one responsibility, new features are added through new classes (not modifying existing ones), and all dependencies are injected through constructors.
AI can also audit existing code for SOLID violations. It identifies classes with multiple reasons to change (SRP violation), methods that check instance types with instanceof (LSP violation), and high-level modules that import low-level modules directly (DIP violation). For each violation, it generates the refactoring plan with specific, testable steps.
Legacy code refactoring is where AI provides the most time savings. Provide the existing code and describe the target architecture. AI generates a step-by-step migration plan: first, write characterization tests that capture current behavior. Then extract interfaces, introduce dependency injection, and move business logic into dedicated service classes.
Each step is a separate, deployable change. AI generates the tests for each step that verify behavior is preserved. The characterization test technique from Michael Feathers' "Working Effectively with Legacy Code" is particularly effective with AI: describe the current behavior and AI produces tests that lock it in before refactoring begins.
AI models are trained on massive codebases that include far more bad code than good code. Without architectural constraints, AI optimizes for "works now" rather than "maintainable later." It puts all logic in a single class, hard-codes dependencies, and avoids abstractions. The fix is providing explicit constraints: "this class must have a single responsibility," "use dependency injection for all external services," "no method should exceed 20 lines." With these rules, AI produces code that follows SOLID principles.
Describe the business problem, not the pattern name. Instead of "implement the Strategy pattern," say "I have three payment processing methods (Stripe, PayPal, bank transfer) that share the same interface but have different implementations. The calling code should not know which processor is being used." AI then produces the interface, concrete implementations, and the context class that delegates to the right strategy, complete with dependency injection configuration.
Yes, through Cursor rules files or Claude project instructions. Define your SOLID rules explicitly: "Every class must have exactly one reason to change (SRP). New payment methods must be added by creating new classes, not modifying existing ones (OCP). All service dependencies must be injected through the constructor (DIP)." AI follows these rules during code generation and can also audit existing code for violations, flagging classes that handle both data access and business logic.
Use factories when object creation involves complex logic, conditional selection of implementations, or when the calling code should not depend on concrete classes. AI helps by analyzing your codebase for repeated new ClassName() calls with conditional logic and suggesting factory extraction. For example, if you have switch statements that create different notification channels based on user preferences, AI refactors this into a NotificationFactory that returns the correct Channel implementation.
Provide AI with the legacy code and describe the target architecture. AI generates a step-by-step migration plan: extract interfaces from concrete classes, introduce dependency injection, move business logic from controllers to service classes, and replace conditional logic with polymorphism. Each step is a separate, testable change. AI also generates the tests that verify behavior is preserved during refactoring, using the characterization test technique from "Working Effectively with Legacy Code."
Absolutely, and AI makes it easy to over-engineer because generating code is cheap. The antidote is YAGNI: You Ain't Gonna Need It. Only introduce a pattern when you have a concrete problem it solves. If you have one payment processor, you do not need a Strategy pattern. If you have one notification channel, you do not need a Factory. AI should be prompted with "apply the simplest solution that solves this specific problem" rather than "what patterns should I use."
Describe your events and their subscribers. "When an order is placed, the inventory system must reserve items, the email system must send a confirmation, and the analytics system must log the event. These systems should not depend on each other." AI generates the event dispatcher, event classes, and listener implementations. In Laravel, it produces Event classes, Listener classes, and the EventServiceProvider registration. In Node.js, it uses EventEmitter or a pub/sub library like Mediator.
Design patterns provide the structure that makes AI-assisted development scalable. Without patterns, AI generates code that works for the immediate task but creates coupling that makes future changes expensive. With patterns, each new feature follows the established architecture. The Adapter pattern wraps third-party APIs so your core code never depends on external libraries. The Repository pattern abstracts data access so you can change databases without touching business logic. Patterns are the guardrails that keep AI productive over time.
Design patterns are not academic exercises. They are the difference between a codebase that scales with your team and one that slows everyone down. Pair patterns with domain-driven design and system architecture for full architectural coverage. Learn to make AI follow the rules that matter.
Get Started