1

I am working on trying to understand the SOLID object oriented design principles. I was able to get the "SOL" pretty easily, though "L" required some hand holding because I am bad at definitions. I think I understand the "I" (I might not, which may belie my confusion). But "D" or Dependency Inversion is tripping me up. Depending on abstractions, rather than concretions makes sense in a way, but the actual application is tripping me up, especially when the term "new is glue" is thrown around in there. Can you tell me where I am going wrong in a code example as I am struggling to make the leap from concept to concrete.

Say I have some abstract class in C#:

  public abstract class IMakeAnIntSetArray
  {
    public void Populate(int FirstItem, int NumberOfItems)
    {
      mySet.Add(FirstItem);
      NumberOfItems--;
      for (int i = 0; i < NumberOfItems; i++)
      {
        mySet.Add(GetNext());
      }
    }

    protected abstract int GetNext();

    protected List<int> mySet;

    public List<int> Set
    {
      get { return mySet; }
    }
  }

It is implemented by the classes

  public class CountNumbersLikeAHuman : IMakeAnIntSetArray
  {
    protected override int GetNext()
    {
      int next = mySet.Last();
      next++;
      return next;
    }
  }

and

  public class SomeMathInvolved : IMakeAnIntSetArray
  {
    protected override int GetNext()
    {
      int next = mySet.Last();
      if(next % 2 == 0)
      {
        return next / 2;
      }
      else
      {
        return ((next * 3) + 1);
      }
    }
  }

Is either of the following a correct way to apply the dependency inversion principle or am I missing something? How would I tweak this to properly utilize the DIP? Or Am I just hung up on "new is glue"?

  class Program
  {
    static void Main()
    {
      CountNumbersLikeAHuman simpleEnough = new CountNumbersLikeAHuman(); //wrong?
      SomeMathInvolved akaChangeYourMajorToEnglish = new SomeMathInvolved(); //also wrong?

      //Or . . . 

      IMakeAnIntSetArray useYouFingersAndToes = new CountNumbersLikeAHuman();
      IMakeAnIntSetArray makeItWayMoreComplicated = new SomeMathInvolved();

      //Or am I completely off here? 
    }
  }
10
  • Take a look at this answer: softwareengineering.stackexchange.com/a/371733/6509. Dependency Inversion is a lot more simple than you've built it up. Sep 26, 2018 at 2:21
  • 2
    Side note: please do not use the I prefix, such as with IMakeAnIntSetArray for abstract classes in C#. I denotes an interface, not an abstract class. You'll cause great confusion for others reading your code if you do this.
    – David Arno
    Sep 26, 2018 at 9:54
  • @DavidArno Yeah. It was originally an interface, the switch was a late thing
    – Jake
    Sep 26, 2018 at 14:14
  • @BerinLoritsch I appreciate that. I think this may be a dupe.
    – Jake
    Sep 26, 2018 at 14:15
  • @BerinLoritsch That answer is about Dependency Injection. It has nothing to do with Dependency Inversion Principle (whatever is understood by the latter).
    – ᄂ ᄀ
    Oct 25, 2018 at 18:24

2 Answers 2

2

I guess you are looking for an example utilizing dependency injection (which is the typical case for using the dependency inversion principle in context of SOLID). For that, you first need two classes which may depend on each other, and where the dependency between them can be resolved by "injecting" objects of the first into the second. The code section in Program then should assemble those parts together.

So the example in your question misses another class where the dependency is injected. For the purpose of demonstration, let us ignore for a moment the fact your abstract base class is misnamed (it is an abstraction for a set of integers, so it should have a name which reflects that, the verb Make in the name makes no sense). Let us also ignore the fact IMakeAnIntSetArray mixes up interface and implementation inheritance, this does not really matter for the following, we can think of IMakeAnIntSetArray as a pure interface, without any code in it.

We start adding another class, lets call it ApplicationForm. Here is an implementation which ignores the DI:

 class ApplicationForm
 {
      IMakeAnIntSetArray _mySet;

      public ApplicationForm(bool countLikeAHuman)
      {
          if(countLikeAHuman)
              _mySet=new CountNumbersLikeAHuman();
          else
              _mySet=new SomeMathInvolved();

      }
      // ... some functions using _mySet
 }

Here, the ApplicationForm class directly depends on CountNumbersLikeAHuman and SomeMathInvolved. It is bound to those two derivations, and it needs to be placed in a libary or layer where those two classes can be referenced directly.

Opposed to that, look at this design:

  class ApplicationForm
  {
      IMakeAnIntSetArray _mySet;

      public ApplicationForm(IMakeAnIntSetArray mySet)
      {
          _mySet = set;
      }
      // ... some functions using _mySet
 }

Now, ApplicationForm does not depend directly on the other two classes any more, it depends only on the abstraction IMakeAnIntSetArray. The caller, however, needs to decide which derivation of IMakeAnIntSetArray will be injected:

class Program
{
  static void Main()
  {
       ApplicationForm appForm;

       // make a decision, for example, by evaluating command like arguments
       appForm = new ApplicationForm(new CountNumbersLikeAHuman());
       //or
       appForm = new ApplicationForm(new SomeMathInvolved());

       // ... use appForm somehow ...
  }

}

As you see, either CountNumbersLikeAHuman or SomeMathInvolved is injected into ApplicationForm. This has several benefits. For example, one could provide a mock implementation of IMakeAnIntSetArray and inject that into ApplicationForm for testing purposes. Or, one could make new derivations of IMakeAnIntSetArray and inject those into ApplicationForm without the need for changing the code of ApplicationForm. This makes it possible to put ApplicationForm into a reusable, black box library, where the lib only contains IMakeAnIntSetArray, but the derivations are up to the caller/user of that library.

1
  • I know it's a separate question, but it has been something I am curious about, but is an abstract class just named as a class?
    – Jake
    Sep 26, 2018 at 16:56
0

In practise it means your code depends on abstraction rather than detail and the client owns the abstraction(the inversion part).

For example say you've got a Class that needs to log some messages. (note code is purely illustrative not advising logging like this )

class DoStuffService{

public void doStuff(){
    FileLogger logger= new FileLogger("C:\mylog.txt");
    Logger.log(DateTime.Now);
}
}

In the above code DoStuffService depends on a concrete File Logger. The dependency is not inverted and the code is inflexible. If you wanted to log to the Windows Event Log instead of the file system you'd need to modify the DoStuffService so it creates a different logger instance.

Here is the code refactored so it uses dependency inversion via dependency injection.

    class DoStuffService{
        ILogger m_logger;

        DoStuffService(ILogger logger){
            m_logger = logger;
        }

        public void doStuff(){
            logger.log(DateTime.Now);
        }

In this code observe that the DoStuffService depends on an ILogger interface(an abstraction) and not a concrete implementation of a logger.

If you wanted to change this code to write to the EventLog you'd simply pass an instance of the EventLogger to it via the constructor. There would be no need to modify the DoStuffService class. Hence it is more flexible and resiliant to change.

Conceptually the code only cares about and depends on an abstraction "Log" it doesnt care about the details and shouldn't be coupled to a specific implementation.

If the project that housed the DoStuffService also housed the ILogger Interface and the concrete implementations of the Loggers were housed in a different project you'd notice the source code dependencies are inverted - The Logger project depends on the DoStuff project hence "dependency inversion".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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