2

Basically we should depend on abstractions instead of concrete classes, that's the Dependency Inversion main principle.

interface ITest
{
    void DoSomething();
}

class TestImpl : ITest
{
    public void DoSomething()
    {}
}

class UseTest
{
    private ITest _test = null;

    public UseTest(ITest test)
    {
        _test = test;
    }

    public void UseCall()
    {
        _test.DoSomething();
    }
}

We'll always have a class that will implement our interface.

So in that case my team could still make bad use of the implementation class:

TestImpl testImpl = new TestImpl();
testImpl.DoSomething();

Instead of the desired approach that would be:

UseTest useTest = new UseTest(new TestImpl());
useTest.UseCall();

If you don't implement a dependency inversion dictatory (everyone should be using abstractions) you've huge chances of something misusing the interface implementation classes.

Is there a way to avoid the misuse of the interface implementation class?

  • Why is the second approach better for you? Both examples call new TestImpl(), so neither is making use of dependency inversion (although UseTest itself is). If you want to make sure that consumers use UseTest instead of TestImpl, start by making UseTest do something useful. – Jacob Raihle Dec 20 '18 at 10:14
6

This is an area where the type system of a language doesn't give you many safeguards. Without making the class a package level case in the case of Java, or an internal class in the case of C#, you can't control who creates instances of this class. A public class with a public constructor can be instantiated by anyone. Even if you make it internal/package-private, any code inside that package or assembly can instantiate it.

This is an issue to resolve during code review. The compiler won't be of much help here, if you aren't using a dependency injection framework that has these kinds of run-time limitations built in.

  • 2
    Just a note. Even with some defensive programming techniques, nothing prevents developers from casting objects to their concrete classes. – Laiv Dec 13 '18 at 14:26
  • 2
    I used to handle this with protected constructors and factories that lived in the same package. I just kept the package small. It is possible to live a happy Dependency Inversion life without mutating the language with crazy frameworks everywhere. – candied_orange Dec 13 '18 at 14:45
  • 1
    @Laiv: Yup. And that is precisely why code reviews are so important. Casting an interface to a concrete class is usually a red flag during code review that requires further investigation by reviewer and code author. – Greg Burghardt Dec 13 '18 at 14:46
  • Agreed. I'm not sure if static analysis also helps. I guess it could. – Laiv Dec 13 '18 at 15:50
2

It depends on the DI framework / library you are using. There may be something in there which helps you prevent misuse of concrete implementations.

Without a DI library, you may consider seperating the classes which are used in a different assembly, apart from the classes which use them and then declare the implementing classes internal.

2

Dependency Inversion principle is more about dependencies between modules/projects/packages/libraries than between classes.

Module/Project Domain

public interface IRepository
{
    void Save(object data);
}

public class Order
{
    private readonly IRepository _repository;

    public Order(IRepository repository) => _repository = repository;

    public void Save(object data) => _repository.Save(data)

}

Module/Project Database

using Domain;

public class SqlServerRepository : IRepository
{
    public void Save(object data)
    {
        // SqlConnection, SqlCommand, Execute ...
    }
}

Application will have only one "main" module - Entry point, which will know about all other modules.
Entry point module will be responsible to build required object graph of your application (instantiate and inject correct implementations)

--------------------------                    ------------------------
|      **Domain**        |                    |     **Database**     |   
|                        |   <--------------  |                      |  
|      IRepository       |                    |                      |  
--------------------------                    ------------------------ 
            ^                                              ^
            |                                              |
            |                                              |
            |            -----------------------           |
            |            |       **Main**      |           |
            |------------|                     |-----------|
                         |                     |       
                         -----------------------

Entry point

using Domain;
using Database;

public class Program
{
    public static void Main()
    {
        var sqlServerRepository = new SqlServerRepository();
        var order = new Order(sqlServerRepository);

        Run(order); // Run appliciation
    }
}

As you can see, there are no way Domain module could instantiate SqlServerRepository.

Dependency Inversion principle applied for the modules will prevent Domain module to instantiate Database module instances only because Domain module will not know about Database module existence.

The idea of Dependency Inversion principle is to change dependency direction we have in runtime during design time.

Runtime dependency direction

Order -> SqlServerRepository

Design time dependency direction

SqlServerRepository -> Order

One of the benefit is reduced building time for big projects when you make changes in a low level modules.
For example: I can make change in Database module without need to re-build Domain module.

Dependence inversion inside one module doesn't have much sense, since classes already know about each other and change in one class will require to re-build a module.

You don't need to write against interface inside one module, unless you actually have different implementations for one abstraction.

1

If you don't want TestImpl to have a publicly callable method; why do you give it a publicly callable method? If you only want UseTest to be able to perform this action, then only UseTest should have a publicly callable method.

There are several ways to avoid this. The main two I can think of are:

  1. Make TestImpl a pure data class and only implement the method logic in UseTest, thus forcing callers to use UseTest if they want to call the method.
  2. Make TestImpl a nested class inside UseTest and set its method to private. UseTest will be able to access TestImpl.DoSomething() but other callers will not.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.