Modern design patterns: Strategy and Factory with dependency injection
Modern design patterns like Strategy and Factory combined with dependency injection create flexible, testable, and maintainable code architectures that adapt seamlessly to changing business requirements without breaking existing implementations.
Modern design patterns like Strategy and Factory with dependency injection represent a powerful combination that transforms how developers build scalable applications. These patterns solve common software design challenges while keeping code clean and adaptable. Understanding how they work together opens doors to creating systems that stand the test of time.
Understanding the Strategy pattern in modern contexts
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This approach allows the algorithm to vary independently from clients that use it.
Core principles of Strategy implementation
Strategy pattern relies on composition over inheritance. Instead of creating multiple subclasses, you define separate strategy classes that implement a common interface. This design choice provides flexibility when switching behaviors at runtime.
- Encapsulates varying behaviors into separate classes
- Enables runtime algorithm selection without conditional statements
- Promotes open/closed principle adherence
- Reduces code duplication across similar implementations
Modern applications benefit from Strategy when dealing with payment processing, data validation, or notification systems. Each scenario requires different approaches that change based on context, making Strategy an ideal solution for managing this complexity.
Factory pattern fundamentals and evolution
Factory pattern provides an interface for creating objects without specifying their exact classes. This abstraction layer separates object creation from business logic.
The pattern evolved from simple Factory Method to Abstract Factory, each serving different complexity levels. Factory Method uses inheritance to decide which class to instantiate, while Abstract Factory uses composition to create families of related objects.
When to apply Factory patterns
Factory patterns shine when object creation involves complex logic or when the exact type of object needed isn’t known until runtime. Database connection managers, UI component generators, and plugin systems commonly leverage this pattern.
- Object creation requires multiple configuration steps
- System needs to support multiple product families
- Code should remain independent of concrete class implementations
- Creation logic centralizes for easier maintenance
Dependency injection as the glue
Dependency injection inverts the control flow by providing objects with their dependencies rather than having them create dependencies internally. This technique dramatically improves testability and flexibility.
Constructor injection, property injection, and method injection represent the three main approaches. Constructor injection proves most reliable because it ensures objects receive all required dependencies before use, preventing incomplete initialization.
Combining dependency injection with design patterns eliminates tight coupling. Instead of Strategy or Factory classes creating their own dependencies, an external container manages object lifecycles and relationships. This separation makes swapping implementations trivial during testing or when requirements change.
Integrating Strategy with dependency injection
When Strategy meets dependency injection, magic happens. The client receives strategy implementations through constructor parameters instead of instantiating them directly.
Practical implementation approach
Define a strategy interface that all concrete strategies implement. The client class accepts this interface through its constructor. A dependency injection container resolves which concrete strategy to inject based on configuration.
- Strategy interface defines the contract all implementations follow
- Concrete strategies implement specific algorithms independently
- Client accepts strategy through constructor injection
- Container configuration determines which strategy gets injected
This architecture allows changing application behavior by modifying container configuration without touching business logic code. Testing becomes straightforward because mock strategies can replace real implementations effortlessly.
Factory pattern with injected dependencies
Factory patterns benefit immensely from dependency injection. Traditional factories often create dependencies internally, creating hidden coupling. Injecting dependencies into factories maintains loose coupling throughout the creation process.
A factory receives its dependencies through constructor injection, then uses them when creating products. This approach ensures created objects receive properly configured dependencies without the factory needing to know configuration details.
Abstract factories particularly benefit from this combination. Each concrete factory implementation can receive different dependencies, allowing the same factory interface to produce vastly different product families based on injected services.
Real-world application scenarios
E-commerce platforms demonstrate these patterns beautifully. Payment processing uses Strategy for different payment methods, Factory for creating payment processors, and dependency injection for providing configuration and logging services.
Building a flexible payment system
A payment strategy interface defines methods like processPayment and validatePayment. Concrete strategies implement credit card, PayPal, and cryptocurrency processing. A payment factory creates appropriate processors based on user selection.
- PaymentStrategy interface standardizes all payment methods
- CreditCardStrategy, PayPalStrategy implement specific logic
- PaymentFactory receives configuration through injection
- PaymentProcessor accepts strategy via constructor
The dependency injection container wires everything together. Configuration determines which factory implementation gets used, which strategies are available, and what services get injected into each component. Changes happen through configuration updates rather than code modifications.
Testing advantages and best practices
Testing code built with these patterns becomes remarkably simple. Mock implementations replace real strategies and factories, allowing isolated testing of individual components.
Unit tests inject test doubles through constructors, verifying behavior without external dependencies. Integration tests use real implementations but inject test configurations. This flexibility ensures comprehensive test coverage without complicated setup procedures.
Best practices include keeping interfaces focused and single-purpose, avoiding service locator anti-patterns, and preferring constructor injection for required dependencies. These guidelines maintain the benefits these patterns provide while avoiding common pitfalls that reduce code quality.
| Pattern Element | Key Benefit |
|---|---|
| Strategy Pattern | Enables runtime algorithm selection and behavior switching |
| Factory Pattern | Centralizes object creation and abstracts concrete classes |
| Dependency Injection | Eliminates tight coupling and enhances testability |
| Combined Approach | Creates flexible, maintainable, and scalable architectures |
Frequently asked questions
Strategy pattern encapsulates each algorithm in separate classes, making them interchangeable and testable independently. Unlike if-else chains that create tight coupling and difficult maintenance, Strategy promotes open/closed principle where new behaviors add without modifying existing code. This separation also enables runtime algorithm selection based on context.
Factory Method works best when creating single products with varying implementations, using inheritance to determine which class to instantiate. Abstract Factory suits scenarios requiring families of related objects that must work together. Choose Factory Method for simpler cases and Abstract Factory when products have interdependencies requiring consistent family creation.
Dependency injection allows replacing real implementations with test doubles through constructor parameters, eliminating the need for complex mocking frameworks or reflection. Tests inject mock objects that verify specific behaviors without external dependencies like databases or APIs. This isolation ensures fast, reliable unit tests that focus on single component functionality.
Absolutely. Strategy and Factory patterns complement each other perfectly when combined with dependency injection. Factories create strategy implementations, dependency injection provides both with required services, and the client receives configured strategies through injection. This combination creates highly flexible architectures that adapt easily to changing requirements while maintaining clean separation of concerns.
Common mistakes include overusing patterns where simple solutions suffice, creating god objects that know too much, using service locator instead of proper injection, and making interfaces too broad. Keep patterns focused on solving specific problems, maintain single responsibility, prefer constructor injection for required dependencies, and design narrow, purpose-specific interfaces.
Bringing it all together
Modern design patterns like Strategy and Factory combined with dependency injection create robust software architectures that adapt gracefully to change. These patterns solve real problems by promoting loose coupling, enhancing testability, and centralizing complex logic. Whether building payment systems, notification services, or data processors, this combination provides the flexibility needed for long-term success. Start implementing these patterns in your next project and experience the difference they make in code quality and maintainability.


