What are hidden dependencies?

The following is the example of a hidden dependency:

class Foo 
{
    void doSomething() //a visible method signature
    {
        //the body of this method is an implementation detail
        //and is thus hidden
        new Bar().doSomething();
    }
}

In the above example, Bar is a dependency for Foo because Foo relies on the collaboration of Bar.

It is hidden because the dependency on Bar is not explicit in the constructor of Foo or method signatures for Foo.

Think of a class as defining a visible contract exposed to collaborators. The methods and constructor signatures are part of that contract. The body of the method doSomething() is hidden because it is an internal implementation detail of the class that is not exposed in the contract. All that we know from the signature is that there is a method called doSomething() that is of return type void.

For a counterexample, we could refactor the class to make the dependencies manifest:

class Foo 
{
    private readonly Bar bar;

    Foo(Bar bar) //the constructor signature is visible
    {
        this.bar = bar;
    }

    void doSomething() 
    {
        bar.doSomething(); 
    }
}

In the above example, Bar is explicitly defined as a dependency in the exposed signature for the constructor.

Alternatively we could do:

class Foo 
{

    void doSomething(Bar bar) //method signature is visible
    {
        bar.doSomething();
    }
}  

Now the dependency on Bar for the method doSomething is visible as it is included in the method signature for doSomething.


Transparent (Concrete) Dependency: A Transparent Dependency is a dependency which is set through a public constructor.

Opaque (Hidden) Dependency: An Opaque Dependency is a dependency that is NOT set through a public constructor, as a result it is not easy to see the dependency

Here is an example:

// Transparent Dependency
public class StudentService
{
    private IStudentRepository _studentRepository;
    public StudentService(IStudentRepository studentRepository)
    {
        _studentRepository = studentRepository;
    }

    public List<Student> GetStudents()
    {
        return _studentRepository.GetAllStudents();
    }
}

// Opaque Dependency
public class StudentService
{
    public List<Student> GetStudents()
    {
        var _studentRepository = new StudentRepository("my-db-name");
        return _studentRepository.GetAllStudents();
    }
}

Opaque Dependecies are considered to be an anti-pattern, this article highlights the problems with Opaque IoC:

  1. Writing tests for component implementing Opaque IoC is much harder

  2. Transparent IoC helps identity classes which are doing "too much"

Mark Seemann describes the second point elegantly:

One of the wonderful benefits of Constructor Injection is that it makes violations of the Single Responsibility Principle glaringly obvious.

Closely related to this is Nikola's 2nd law of IoC:

Any class having more than 3 dependencies should be questioned for SRP violation