
Control Freak Antipattern in Dependency Injection
Author - Abdul Rahman (Bhai)
Dependency Injection
7 Articles
Table of Contents
What we gonna do?
When a class insists on creating its own dependencies instead of receiving them from outside, it becomes a Control Freak. In this article, let's learn about the Control Freak anti-pattern in Dependency Injection and understand why it violates the principles of loose coupling and Inversion of Control.
Why we gonna do?
The Control Freak anti-pattern is the exact opposite of Inversion of Control. When a class creates its own dependencies using the new keyword, factory methods, or service locators, it takes control over the dependency creation process instead of allowing external configuration.
This anti-pattern makes code tightly coupled, difficult to test, and hard to extend. It prevents you from swapping implementations, makes unit testing nearly impossible without modifying the class, and violates the Open/Closed Principle.
Common Manifestations of Control Freak
The Control Freak anti-pattern appears in several forms:
- Newing up dependencies - Direct instantiation using the new keyword
- Factory-based creation - Using concrete or abstract factories within the class
- Overloaded constructors - Providing parameterless constructors that create default dependencies
How we gonna do?
Example 1: Control Freak Through Newing Up Dependencies
Here's a classic example of the Control Freak anti-pattern where a ProductService creates its own repository:
public class ProductService
{
private readonly IProductRepository repository;
public ProductService()
{
// Control Freak: Creating dependency directly
this.repository = new SqlProductRepository();
}
public Product GetFeaturedProduct()
{
return repository.GetFeaturedProduct();
}
}
Problems with this approach:
- Tightly coupled to SqlProductRepository
- Cannot be unit tested with a test double
- Cannot switch to a different repository implementation
- Violates the Dependency Inversion Principle
Example 2: Control Freak Through Concrete Factory
Sometimes developers try to solve the coupling issue by introducing a factory, but still maintain control:
public class ProductService
{
private readonly IProductRepository repository;
public ProductService()
{
// Still Control Freak: Using concrete factory
var factory = new ProductRepositoryFactory();
this.repository = factory.Create();
}
public Product GetFeaturedProduct()
{
return repository.GetFeaturedProduct();
}
}
public class ProductRepositoryFactory
{
public IProductRepository Create()
{
return new SqlProductRepository();
}
}
This approach still exhibits Control Freak behavior because the class decides which factory to use and when to call it.
Example 3: Control Freak Through Abstract Factory
Even using an abstract factory can be a form of Control Freak if the class controls the factory selection:
public abstract class ProductRepositoryFactory
{
public abstract IProductRepository Create();
}
public class ProductService
{
private readonly IProductRepository repository;
public ProductService()
{
// Control Freak: Deciding which factory to use
ProductRepositoryFactory factory;
if (ConfigurationManager.AppSettings["DatabaseType"] == "SqlServer")
factory = new SqlProductRepositoryFactory();
else
factory = new AzureProductRepositoryFactory();
this.repository = factory.Create();
}
}
Example 4: Control Freak Through Overloaded Constructors
Another common manifestation is providing a parameterless constructor that creates default dependencies:
public class ProductService
{
private readonly IProductRepository repository;
// Control Freak: Parameterless constructor creates default dependency
public ProductService() : this(new SqlProductRepository())
{
}
// Proper constructor for DI
public ProductService(IProductRepository repository)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public Product GetFeaturedProduct()
{
return repository.GetFeaturedProduct();
}
}
While this seems to offer flexibility, the parameterless constructor represents a Foreign Default because it creates a dependency defined in a different module.
Refactoring Away from Control Freak
The solution is to apply proper Constructor Injection and let the Composition Root or Dependency Injection Container handle dependency creation:
// Properly designed ProductService
public class ProductService
{
private readonly IProductRepository repository;
public ProductService(IProductRepository repository)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public Product GetFeaturedProduct()
{
return repository.GetFeaturedProduct();
}
}
// Composition Root handles dependency creation
public class CompositionRoot
{
public ProductService CreateProductService()
{
IProductRepository repository = new SqlProductRepository();
return new ProductService(repository);
}
}
Benefits of Proper Dependency Injection
By eliminating the Control Freak anti-pattern, you gain:
- Testability - Easy to inject test doubles
- Flexibility - Can swap implementations without changing the class
- Maintainability - Dependencies are explicit and clear
- Single Responsibility - Class focuses on its core logic, not dependency management
Testing the Refactored Code
With proper dependency injection, unit testing becomes straightforward:
[Test]
public void GetFeaturedProduct_ReturnsExpectedProduct()
{
// Arrange
var expectedProduct = new Product { Id = 1, Name = "Test Product" };
var mockRepository = new Mock<IProductRepository>();
mockRepository.Setup(r => r.GetFeaturedProduct()).Returns(expectedProduct);
var productService = new ProductService(mockRepository.Object);
// Act
var result = productService.GetFeaturedProduct();
// Assert
Assert.AreEqual(expectedProduct, result);
}
Summary
The Control Freak anti-pattern occurs when a class takes control of creating its own dependencies instead of receiving them from external sources. This violates the principles of Inversion of Control and leads to tightly coupled, hard-to-test code.
Whether through direct instantiation, factory methods, or parameterless constructors, Control Freak behavior makes code rigid and difficult to maintain. The solution is to embrace Constructor Injection and delegate dependency creation to the Composition Root, resulting in more flexible, testable, and maintainable code.