left-icon

SOLID Principles Succinctly®
by Gaurav Kumar Arora

Previous
Chapter

of
A
A
A

CHAPTER 8

Dependency Inversion Principle

Dependency Inversion Principle


The Dependency Inversion Principle is related to decoupling. Here is Robert C. Martin on DIP:

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

In thinking about this, let's have a look at our EmailNotification example once again and consider why our code should decide where to send email (SMTP server or a printer) at the very beginning. Why should it not automatically perform the preferred action?

We’ll examine those details later in this chapter, but first let me address the confusion many developers have about DIP and Inversion of Control. I’ve received many emails and messages on my social pages asking about their differences. Simply put, the Dependency Inversion Principle is a software design principle and Inversion of Control is a software design pattern.

Again, let’s think about the original validator real-time scenario we’ve discussed in previous chapters. We are now adding a new element, writing the validation results by choosing ValidatorWriters.

Note: Different validators have different writers.

Code Listing 41

public class DataValidator : IDataValidator

    {

        private IValidatorWriter writer;

        public void Validator(ValidatorWriter validatorWriter)

        {

            //Write stuff to validate data.

            string validationResults = ValidateData();

            //Write validation results.

            WriteValidationResults(validatorWriter, validationResults);

        }

        private void WriteValidationResults(ValidatorWriter validatorWriter, string validationResults)

        {

            if (validatorWriter == ValidatorWriter.FileValidatorWriter)

                writer = new FileValidatorWriter();

            else if (validatorWriter == ValidatorWriter.TypeValidatorWriter)

                writer = new TypeValidatorWriter();

            else if (validatorWriter == ValidatorWriter.DataValidatorWriter)

                writer = new DataValidatorWriter();

            else if (validatorWriter == ValidatorWriter.IPValidatorWriter)

                writer = new IPValidatorWriter();

            else if (validatorWriter == ValidatorWriter.SchemaValidatorWriter)

                writer = new SchemaValidatorWriter();

            else if (validatorWriter == ValidatorWriter.CodeValidatorWriter)

                writer = new CodeValidatorWriter();

            else

                throw new ArgumentException("No matched validator found.", validatorWriter.ToString());

            writer.Write(validationResults);

        }

        private string ValidateData()

        {

            throw new NotImplementedException();

        }

    }

So, we have a DataValidator class that implements an interface IDataValidator and performs the specific operations.

Code Listing 42

public interface IDataValidator

    {

        void Validator(ValidatorWriter validatorWriter);

    }

public interface IValidatorWriter

    {

        void Write(string strText);

    }

The IValidatorWriter interface actually provides the method to write all validation results.

Code Listing 43

public class CodeValidatorWriter : IValidatorWriter

{

    public void Write(string strText)

    {

       throw new NotImplementedException();

    }

}

public class SchemaValidatorWriter : IValidatorWriter

{

    public void Write(string strText)

    {

       throw new NotImplementedException();

    }

}

public class TypeValidatorWriter : IValidatorWriter

{

   public void Write(string strText)

   {

      throw new NotImplementedException();

   }

}

Here is our implementation of the IValidatorWriter interface. Be sure to note SchemaValidatorWriter and TypeValidatorWriter—both have different ways of writing validation rules, which means there are different implementations of the Write() method for each one.

But what principle is violated here?

There is no doubt that our code is violating SRP. Here we are actually deciding which object should be created as per the enum ValidatorWriter.

In Code Listing 44, we should create an object of class FileValidatorWriter but not TypeValidatorWriter.

Code Listing 44

public enum ValidatorWriter

{

   TypeValidatorWriter = 1,

   DataValidatorWriter = 2,

   IPValidatorWriter = 3,

   SchemaValidatorWriter = 4,

   CodeValidatorWriter = 5,

   FileValidatorWriter = 6

}

While we are making a decision here, we are in fact ignoring the actual DataValidator class concept, which is meant to validate the data and not to write validation results. But we are making the decision on the basis of the incoming type of ValidatorWriter, then performing Write() after the creation of an object of specific class, which is wrong and violates SRP.

So, what would be the best solution?

Let’s review. Note that the DataValidator class is currently responsible for deciding which writer should be created to write for specific validation results. So we found the solution—let’s withdraw these rights from the DataValidator class and introduce something new that will decide which class object should be created to write for specific validation results.

And, because we are delegating the responsibility to a new class, we cannot force our existing class to play more operations.

We can envision an implementation in which the client makes the call and tells us what kind of writer is required for this validator. In Code Listing 45, we rewrite our DataValidator class.

Code Listing 45

using System;

namespace DIP_Follow

{

    public class DataValidator : IDataValidator

    {

        private readonly IValidatorWriter _writer;

        public DataValidator(IValidatorWriter writer)

        {

            _writer = writer;

        }

        public void Validator(ValidatorWriter validatorWriter)

        {

            //Write stuff to validate data.

            string validationResults = ValidateData();

            //Write validationResults.

            _writer.Write(validationResults);

        }

        private string ValidateData()

        {

            throw new NotImplementedException();

        }

    }

}

In Code Listing 45, we wrote clean code—it’s concise, and we removed all those dirty if..else if..else blocks., Our DataValidator class knew which kind of writer we should use to write validation results. Our client should ship this with their call, something like this: var obj = new DataValidator(new SpecificWriter());.

Code Listing 46 is our complete call from the client.

Code Listing 46

using System;

namespace DIP_Follow

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Start writing validation results.");

            IDataValidator dataValidator = new DataValidator(new SchemaValidatorWriter());

            Console.ReadLine();

        }

    }

}

In the preceding code examples, in places where our code violated DIP, we were explicitly checking the type of writer, but here in Code Listing 46 we do not need to handle that scenario in our code.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.