Compose, Don’t Combine
Sometimes a single word can land us in hot water. It happened to me with an article on Bloated Constructors. One of the solutions I proposed was:
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.
The word that got me into trouble in the above sentence is ‘combine‘.
My friend Andy pointed out that we would significantly decrease ease of maintenance if
IEmailTemplateService implementations were combined into a single class
EmailTemplateManager. Of course, Andy is right—it would probably be suboptimal to consolidate these classes. Hat tip to Andy.
There are probably good reasons why the interfaces and their corresponding implementers are kept separate. Unfortunately, we can’t tell much about implementation behaviour from a mere interface.
In any case, what I meant to say is that the functionality provided by the
IEmailTemplateXXX interfaces could potentially be composed into another high-level class. It would depend on whether that class had a good, singular reason to exist, i.e., a cohesive class. Let’s say that
EmailTemplateBuilder is such a cohesive type utilising the functionality of
The constructor signature for
EmailTemplateBuilder might look like this:
public EmailTemplateBuilder(IEmailTemplateRepository emailTemplateRepository, IEmailTemplateConfiguration emailTemplateConfig, IEmailTemplateService emailTemplateService)
Here is a reproduction of our original, 10-parameter bloated constructor for
public CustomerEmailService(ICustomerRepository customerRepository, ICustomerService customerService, ICustomerTransactionRepository customerTransactionRepository, IEmailTemplateRepository emailTemplateRepository, IEmailTemplateConfiguration emailTemplateConfig, IEmailTemplateService, emailTemplateService, IReportingService reportingService, IConfigurationService configService, ILocalTimeProvider localTimeProvider, ILogger logger)
IEmailTemplateBuilder, and we managed to switch out
IEmailTemplateBuilder for 3
IEmailTemplateXXX interfaces in
CustomerEmailService, then the constructor becomes less bloated, contracting to 8 parameters:
public CustomerEmailService(ICustomerRepository customerRepository, ICustomerService customerService, ICustomerTransactionRepository customerTransactionRepository, IEmailTemplateBuilder emailTemplateBuilder, IReportingService reportingService, IConfigurationService configService, ILocalTimeProvider localTimeProvider, ILogger logger)
CustomerEmailService constructor is still bloated. There is more work to be done. However, it’s a noticeable improvement.
By the way, the title of this article seems to suggest that composition is always the right way to go—not so. At times it might make perfect sense to merge the code of two classes. For example, a
ShoppingCartManager class and a
ShoppingCartHelper class, each performing a complementary part (but not the whole) of a shopping cart’s functionality, say, might be absorbed into a cohesive
Unfortunately, there are no rules about how to construct the perfect abstraction or class every time. That does not mean anything goes, and we are entirely on our own. On the contrary—we have fabulous tools like The SOLID Principles. Sometimes we combine, sometimes we compose, but as long as the code ‘makes sense’—our classes are small and cohesive—we are on a good path.