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:
- 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
, theIEmailTemplateXXX
interfaces might be fertile ground for such a rationalisation. - 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.
Leave a Reply
Want to join the discussion?Feel free to contribute!