Watch Out For Bloated Constructors

Watch Out For Bloated Constructors

 

Do you ever see this kind of bloated constructor?

  public CustomerEmailService(
     ICustomerRepository customerRepository,
     ICustomerService customerService,
     ICustomerTransactionRepository customerTransactionRepository,
     IEmailTemplateRepository emailTemplateRepository,
     IEmailTemplateConfiguration emailTemplateConfig,
     IEmailTemplateService, emailTemplateService,
     IReportingService reportingService,
     IConfigurationService configService,
     ILocalTimeProvider localTimeProvider,
     ILogger logger)
  {

CustomerEmailService does too much.

When a class depends on ten interfaces, it is a volatile type.

Volatile? How?

Each dependent interface is subject to change pressure. Actively developed software experiences periodic modification due to changes in customer requirements. Sooner or later, an interface a class depends on will be modified. The more interfaces a class references, the more likely it will be affected by these changes. Therefore, a type with ten dependencies will be more volatile than one with only a single dependency.

 

The more dependencies a module has, the more volatile it is. 

 

Not all modifications are equal. 

An interface with a newly added method that CustomerEmailService does not call will not require code changes in CustomerEmailService. However, in a compiled language like C#, C++ or Java, a recompilation and redeployment of CustomerEmailService will be necessary. All that even though CustomerEmailService is not interested in this new method.

 

On the other hand, when an existing and called method on one of the dependent interfaces changes, the effect will be more severe—compilation errors in CustomerEmailService.

  

OK. Let’s get concrete:

From the constructor listing above, we can see that CustomerEmailService depends on interface ICustomerRepository—the first parameter. Say,ICustomerRepository exposes method GetCustomer():

  Customer GetCustomer(Guid customerId);

We know that CustomerEmailService calls ICustomerRepository.GetCustomer() several times.

Let’s assume we change the method signature of ICustomerRepository.GetCustomer() to

  Customer GetCustomer(CustomerIdentifier customerId); 

The parameter type has changed from Guid to CustomerIdentifier. 

What will this mean for CustomerEmailService? We now have compilation error inside CustomerEmailService wherever ICustomerRepository.GetCustomer() is called.

 

Changes to interfaces affect clients of these interfaces. 

 

The more interfaces a client depends on, the more likely it is to be affected. 

It’s a crucial point: Even though CustomerEmailService only depends on abstractions (i.e. interfaces), it does not eliminate all Coupling. Certainly, we are less coupled depending on interfaces than on concrete classes.

Yet, a class referencing many interfaces is still affected by modifications to any one of those interfaces.

OK, how do we overcome dependency volatility?

 

Simple—reduce the number of dependencies. Decrease dependencies to a small number—fewer than 4, in most cases. Up to 5 might be OK from time to time. Six or larger is too many—find the unexploited opportunity for rationalising dependencies.

  

How do we reduce the number of dependencies?

Here are two ideas:

  1. Bottom-Up / Combine Dependencies: Can some of the dependent services and repositories be isolated and managed together via a new high-level class? E.g. In CustomerEmailService, the IEmailTemplateXXX interfaces might be fertile ground for such a rationalisation.
  2. Top-Down / Break Up Dependent Class: Can we take a class with too many dependencies and break it into smaller, more focused classes, each with only a small number of dependencies? This approach could work for CustomerEmailService too.

 

Please watch out for bloated constructors. It means trouble ahead.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply