Introduction
If a component or class depends upon other components to complete its operations, then these other components are dependencies for this class. Our class can have these dependencies as implicit or explicit dependencies. In this article, we will try to understand how it affects our application design.
Implicit Dependencies
The dependencies are implicit for a class if they exist only in the code within that class, and not in its public interface. Therefore, while instantiating our class, we will not be aware of any dependency that our class may have to perform its operations. Consequently, our code may fail during execution.
Classes with implicit dependencies cost more to maintain than those with explicit dependencies. Also, they are more difficult to test because of their tight coupling with the collaborators. And the tight coupling among the classes and their collaborators result in more rigid and brittle designs.
Example
- class Program
- {
- static void Main(string[] args)
- {
- decimal paidValue = 453.23m;
- var commerce = new Commerce();
- commerce.ProcessCustomerPayment(paidValue);
- }
- }
-
- public class Commerce
- {
- public void ProcessCustomerPayment(decimal paidValue)
- {
- var currencyConverter = new CurrencyConverter();
- var paymentProcessorv = new PaymentProcessor();
- decimal currencyValue = currencyConverter.ConvertCurrency(paidValue);
- paymentProcessor.ProcessPayment(currencyValue);
-
- }
- }
-
- public class PaymentProcessor
- {
- public void ProcessPayment(decimal value)
- {
-
- }
- }
-
- public class CurrencyConverter
- {
- public decimal ConvertCurrency(decimal value)
- {
-
- return value;
- }
- }
As we can see, the Commerce class is tightly coupled with the PaymentProcessor and CurrencyConverter. Also, at the time of object creation for Commerce class, we can not judge what all collaborators it is going to use. Further, we will see how we can refactor this code in order to achieve loose coupling.
Explicit Dependencies
Being explicit about its dependencies means that a class exposes all its class-level dependencies in its constructor. However, more local ones may appear as method parameter list. It is due to the explicit declaration that we are aware of all dependencies a class has, at the time of its object construction.
An explicit dependency is often declared as an interface in the class constructor. Consequently, the dependency can be easily swapped out with its other implementations, whether in production or during testing or debugging. This makes them much easier to maintain and far more open to accepting any change.
Example
- class Program
- {
- static void Main(string[] args)
- {
- decimal paidValue = 453.23m;
- var commerce = new Commerce(new PaymentProcessor(), new CurrencyConverter());
- commerce.ProcessCustomerPayment(paidValue);
- }
- }
-
- public class Commerce
- {
- IPaymentProcessor _paymentProcessor;
- ICurrencyConverter _currencyConverter;
-
- public Commerce(IPaymentProcessor paymentProcessor, ICurrencyConverter currencyConverter)
- {
- _paymentProcessor = paymentProcessor;
- _currencyConverter = currencyConverter;
- }
-
- public void ProcessCustomerPayment(decimal paidValue)
- {
-
- decimal currencyValue = _currencyConverter.ConvertCurrency(paidValue);
- _paymentProcessor.ProcessPayment(currencyValue);
- }
- }
-
-
- public interface IPaymentProcessor
- {
- void ProcessPayment(decimal value);
- }
-
- public class PaymentProcessor : IPaymentProcessor
- {
- public void ProcessPayment(decimal value)
- {
-
- }
- }
-
- public interface ICurrencyConverter
- {
- decimal ConvertCurrency(decimal value);
- }
-
- public class CurrencyConverter : ICurrencyConverter
- {
- public decimal ConvertCurrency(decimal value)
- {
-
- return value;
- }
- }
In this example, the Commerce class exposes all its required collaborators in its constructor. As a result, we have a loosely coupled application. Moreover, we now have the freedom to choose among different implementations of these interfaces required by the Commerce class. Consequently, the application is now easy to maintain, test and expand.
Summary
In this article, we learned about the two different ways in which a component can expose its dependencies. However, it is a good practice to explicitly expose the dependencies of a component. It makes our components easy to test, maintain and enhance.
We are now familiar with types of dependencies and have also studied the
problems associated with a tightly coupled application. In the next part of this series, we will decouple our application without using any dependency injection container. We will also enhance our application for future purpose.