What are dependencies?

Alen IBRIC
5 min readDec 29, 2023

In programming, “dependency” represents the relationship between different components in a software system. It is considered that when one component uses or relies on another component, there is a dependency between them.

Dependencies can take many forms, and the basic types of dependencies include:

  1. Class Dependency: This refers to a situation where one class uses another class. For example, if you have a class representing a user and another class managing a database (DatabaseManager), the user class may depend on the database management class to store or retrieve information.
  2. Module Dependency: This occurs in larger systems where different components are often organized into modules. Dependency between modules can occur when one module uses the functionalities or services of another module.
  3. Method Dependency: Arises when one method calls or uses another method.
  4. Service Dependency: Occurs when applications use different services, such as data management services, authentication services, or messaging services. Service dependency occurs when a piece of code (e.g., a class) uses the functionalities or services of a service.

Dependencies themselves are not inherently bad, but it is important to manage them in a way that facilitates code maintenance and testing. However, excessive dependency and “strongly coupled code” between components can make code changes and unit testing more challenging.

Component Coupling

“Strongly Coupled Code” refers to a situation in programming where system components have strong dependencies on each other, meaning one component uses another component, or when one module relies on another module. In such cases, changes in one part of the code often require changes in other parts that depend on it.

The drawback of this design is that maintaining code written in this way can be challenging because changes in one part of the code may have cascading effects on other dependent parts. Additionally, flexibility is reduced because replacing or upgrading parts may necessitate broader changes throughout the entire system.

An example of “Strongly Coupled Code” is “direct instantiation of objects within one component” that depends on the implementations of other components, or the direct use of concrete classes instead of abstractions or interfaces for communication between components.

Loosely Coupled Code

Loosely Coupled Code refers to a design where system components have minimal dependencies on each other, so changes in one part of the code should not cause significant changes in other parts.

In this design, components often communicate through interfaces or abstractions rather than directly relying on the implementations of other components.

The advantage of this design is noticeable in maintenance because changes in one part of the code will not automatically require changes in other parts. This increases the flexibility of the system, as it becomes easier to replace or upgrade parts without affecting the entire system.

What is “Dependency Injection”?

Dependency Injection” (DI) is a software design pattern based on the principle of passing objects to other components that need them, rather than components creating them internally. This approach contributes to reducing mutual dependencies between components, leading to “Loosely Coupled Code.” Classes that are dependent (dependencies) and whose instances are passed must have interfaces or be abstract themselves. This programming approach also facilitates unit testing because it allows for easy insertion of fake (mock) dependency implementations during testing.

There are three main ways of injecting dependencies from the outside:

  1. Constructor Injection: Dependencies are injected through the class constructor.

This principle allows objects to be instantiated with pre-set dependencies, providing greater flexibility, maintainability, and testing in software projects.

public class SomeClass
{
private readonly IDependency dependency;

public SomeClass(IDependency dependency)
{
this.dependency = dependency;
}
}

Method Injection: Dependencies are injected through methods.

public class SomeClass
{
public void DoSomething(IDependency dependency)
{
// Use the dependency
}
}

Property Injection: Dependencies are injected through properties.

public class SomeClass
{
public IDependency Dependency { get; set; }
}

Examples

In this example, we’ll consider two interrelated classes: the “Car” class and the “Engine” class. It’s evident that the “Car” class depends on the “Engine” class, meaning that the “Engine” is a dependency of the “Car” class. In these examples, the “Engine” class has only one method called “Start,” and it is precisely this method that the “Car” class requires.

Example of Strongly Coupled Code

In a strongly coupled scenario, the “Engine” class might look like this:

public class Motor
{
public void Start()
{
// Start the engine
Console.WriteLine("Engine started!");
}
}

In “Strongly Coupled Code” design, the dependent object is directly instantiated, and in this example, we will directly instantiate the “Engine” object within the constructor:

public class Automobil
{
private readonly Motor motor;

public Automobil()
{

// Example of strong dependency because the instance is created

// in the constructor

this.motor = new Motor();
}
public void Start()
{
motor.Start();
}
}

So, in the main program, it would look like this:

class Program
{
static void Main()
{
Automobil automobil = new Automobil();
automobil.Start();
}
}

This code looks perfectly fine until there’s a need to expand the application’s capabilities, like introducing a new type of engine, for example, an electric one. At that point, a problem arises because, in addition to the new engine class, changes need to be made in the Car class. Most likely, this would result in creating two separate car classes, each with its own type of engine.

Example of Loose Coupling “Loosely Coupled Code” with Dependency Injection

In this example, we need to add an interface for the Start method:

public interface IMotor
{
void Start();
}

So, we will change the “Engine” class to implement this interface:

public class Motor : IMotor
{
public void Start()
{
// Start the engine
Console.WriteLine("Engine started!");
}
}

With dependency injection, we can easily add new engines by creating a new class for the new engine, for example, “ElectricEngine,” which implements the same IEngine interface:

And the class that implements this interface:

public class MotorElektricni : IMotor
{
public void Start()
{
// Start the engine
Console.WriteLine("Electric Engine started!");
}
}

So, the Car class will now have the following form:

public class Automobil
{
private readonly IMotor motor;

public Automobil(IMotor motor)
{
this.motor = motor;
}
public void Start()
{
motor.Start();
}
}

In the main program, it is now evident that an instance of the “Engine” class is passed as a parameter to the “Car” class:

class Program
{
static void Main()
{

// Creating an instance of the engine:
IMotor motor = new Motor();
IMotor elektricniMotor = new MotorElektrični();

// Injecting the instance into the car through the constructor:
Automobil automobil = new Automobil(motor);
Automobil elektricniAutomobil = new Automobil(elektricniMotor);

automobil.Start();
elektricniAutomobil.Start();
}
}

CONCLUSION:

In the example with Dependency Injection (“Loosely Coupled Code”), the Car class uses the IEngine interface, and the concrete implementation (Engine) is injected through the constructor. This allows for a straightforward replacement of the engine implementation.

On the other hand, in the example without Dependency Injection (“Strongly Coupled Code”), the Car class directly instantiates the Engine, making the code less flexible. If you want to change the type of the engine, you would need to directly modify the Car class.

The use of Dependency Injection provides greater flexibility and simplifies testing and code maintenance, making it less dependent on specific implementations.

--

--