S.O.L.I.D – The Dependency Inversion Principle (DIP)




Abstract

This article is the 7th of a series of seven articles I wrote (described later on) about The Principles of Object Oriented Design, also known as SOLID.

This post describes the Dependency Inversion Principle (DIP).


As a prerequisite, please read the first 2 articles:
           2.     Software Design Smells


As architects and solution designers we’d like to design and write robust & clean solutions.

We’d like our code to be stable, maintainable, testable and scalable for as long as our product lives.

We’d like to implement and integrate new features as smoothly as possible in our existing (already tested and deployed) code, without any regression issues, and by maintaining dead-lines and production deployments.

In addition, we’d like our code to perform according to standard performance KPIs, and eventually, to serves its purpose.

Such desire has many obstacles, especially as our application evolves and our product become more complex.

More often than we’d like, we are experiencing these obstacles which could be described as software design smells.

The purpose of the SOLID principles is to achieve these desires and to prevent software design smells.

In this article series we’ll emphasize the importance of well-designed applications.

We’ll show how, by maintaining the SOLID principles in our code, we’ll create better robust solutions, provide easier code maintenance, faster bug fixing and the overall impact on the application performance, especially while adding new features to existing design, as the application evolves with new requirements.



The complete series:
           
Intro:
               1.      SOLID - The Principles of Object Oriented Design
               2.      Software Design Smells

S.O.L.I.D
               3.      The SingleResponsibility Principle (SRP)
               4.      The Open-ClosePrinciple (OCP)
               5.      The LiskovSubstitution Principle (LSP)
               6.      The InterfaceSegregation Principle (ISP)
               7.      The DependencyInversion Principle (DIP)




Content

  1. Introduction
-        Application Layers
-        Dependency Between Layers
-        Preventing Dependency
  1. Case Study
-        Requirements
  1. A design that conforms to the DIP
-        Description
-        Important: Additional Layers
  1. Implementation
1.      Books Services Layer
2.      Books Operations Layer
3.      Books Validation Layer
4.      Testing
o   Mocking using Moq
  1. IoC Containers, DI Containers & Service Locators
  2. Summary





Introduction

The dependency inversion principle states that:

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

Abstractions should not depend upon details. Details should depend upon
abstractions.


Application Layers

When we design software applications, most often we use a ‘Layering Techniques’, in which we create different layers that provide services to other layers in the application architecture.

A high-level layer uses a low-level layer, which in turn, uses another lower layer, and so on.

This means that the upper-level layer depends on its corresponding lower-level layer, since it uses its services.

Simply put, we have a high-level layer A, which defines some of the application business logic, and uses a lower-level layer B, which exposes different services for the upper-level layer A use.

It’s important to understand that the business logic design is implemented in the high-level layers, whereas the lower-level layers provide the implementation details for this purpose.

A layer could be designed & implemented as a module, several modules, a class or any other relevant software entity for that matter.
  

Dependency Between Layers

The dependency inversion principle indicates that the upper business logic layer should NOT depend on the lower-level layer.

Such dependency could lead to undesired modifications in the upper business logic layer, with every change in the low-level layer, which would have a direct effect on the business logic implementation.

Naturally, this could break the business logic requirements, and would eventually lead to software design smells.

In addition, in case we need to reuse the high-level business logic layer, a dependency to lower-level layers could create severe issues, sometimes even impossible to reuse, and again would lead to software design smells.


Please see the following illustration:





Preventing Dependency

How could we prevent the high-level layer from depending on its corresponding lower-level layer?

Well, the answer is by using abstraction.


When we’ll design a layer that needs to use a lower-level layer, we could design an abstract interface(s) that the upper-level layer would need.

The lower-level layer would have to provide its services based on this abstract interface(s), which was declared on the upper-level layer.

This means that each upper-level layer would use its corresponding lower-level layer services through those abstract interfaces, thereby preventing the dependency on the lower-level layers.


Please see the following illustration:





  
Important: Independent Packages

The fact that the upper-level layer declares the interfaces it needs seems a bit inverted and could sometime be confusing, since we are used to declare the interfaces in the layer that provides the services, meaning the lower-level layer (on our case), and not on the client (consumer) that uses those services, meaning the upper-level layer.

Furthermore, this design would result in a dependency of the lower-level layer on the upper-level layer, which we should also prevent, since in case there are several upper-level layers that consume the lower-level layer, we would have to duplicate the interfaces, which would eventually lead to severe issues.

Thus, it’s advisable to declare the interfaces on different independent packages, that both the upper-level and the lower-level layers could use as required.
  
   
Object Composition

As indicated in the ‘Application Layers’ section above, a layer could also be a class that uses another class services, meaning the upper-level class contains a reference to another lower-level class, and uses its services.

In other words, we designed an Object Composition relationship between those classes.

This means that we should apply the Dependency Inversion Principle in such designs as well, and use abstraction to prevent dependency to the lower-level class reference.

We should use dependency-injection techniques as I’ll demonstrate in the following case study example.
  

  
Case Study

We’ll illustrate the dependency inversion principle using a simple library-management project.
The library contains books that users could borrow.


Requirements:

1.      We need to create a service that exposes relevant operations, such as: BorrowBook, ReturnBook, CreateBook, ReadBook etc.
-        This service would simulate the upper-level business logic layer.

2.      Different clients could consume this service.
-        For example, an Angular-2 application that invokes this services operations.

3.      We need to implement the different books operations.
-        We’ll add a detailed operations implementation layer.

4.      We need to validate the requests arrived from the clients.
-        We’ll add a validation layer.



Remarks:
-        There are different appropriate designs we could consider (based on the requirements), however for simplicity and for our illustration we’ll use the following.

-        I won’t implement the entire solution, only emphasize the use of the ‘layering technique’ and how to conform to the dependency inversion principle.




A design that conforms to the DIP







Description:

1.     Books Services Layer

-        We added a BooksServices class that would be implemented as a WCF, MVC or WebApi service.

-        The BooksServices class contains 2 interface-composition objects: IBooksBorrowOperations, IBooksCRUDOperations.

-        These interfaces contain the abstraction of all the operations that the BooksServices class needs.

-        Meaning, classes that would implement these interfaces could be used in the Books Services Layer.


2.     Books Operations Layer

-        We added the BooksOperationsManager class that implements both the IBooksBorrowOperations and IBooksCRUDOperations interfaces.

-        In addition, the BooksOperationsManager class contains 2 interface-composition objects: IBooksBorrowOperationsValidator, IBooksCrudOperationsValidator.


3.     Books Validation Layer

-        We added the BooksBorrowOperationsValidator class that implements the IBooksBorrowOperationsValidator interface.

-         We added the BooksCrudOperationsValidator class that implements the IBooksCrudOperationsValidator interface.



Remark:

As described in the Important: Independent Packagessection above, these interfaces implementation is best practiced in a separate assembly, however for simplicity and for our demonstration, I implemented all these classes and interfaces in the same assembly.



Important: Additional Layers

This design is very flexible and easy scalable, since we could use it to add additional relevant layers as needed.

For example, in order to build the operations’ requests we could add a Books Adapter Layer.

In this layer we’ll build the operations’ requests as required, which means that we’ll extract this ‘detailed implementation’ from the Books Operation Layer to a separated layer that could be abstracted (using interfaces) and expand as requirements would evolved.

Similarly, we could add additional layers, such as:

-        Books Permissions Layer – that determines who could perform what.
-        Books DBTasks Layer – that encapsulate the DAL layer for DB operations.
-        Books Auditing Layer – that invokes all relevant auditing & logging listeners.
-        Etc.




 Implementation


1.    Books Services Layer


BooksServices.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;
using DependencyInversionPrinciple_Example.Operations.Books;
using System;

namespace DependencyInversionPrinciple_Example.Services.Books
{
    // TODO: Need to implement as a WCF, MVC or WebApi service...


    public class BooksServices
    {
        // Private readonly variables

        private readonly IBooksBorrowOperations _booksBorrowOperations;
        private readonly IBooksCRUDOperations _booksCRUDOperations;


        // Constructors

        public BooksServices(IBooksBorrowOperations booksBorrowOperations = null,
                             IBooksCRUDOperations booksCRUDOperations = null)
        {
            // Setting default IBooksBorrowOperations
            // implementation, in case wasn't provided.
            _booksBorrowOperations =
                booksBorrowOperations ?? new BooksOperationsManager();

            // Setting default IBooksCRUDOperations
            // implementation, in case wasn't provided.
            _booksCRUDOperations =
                booksCRUDOperations ?? new BooksOperationsManager();
        }


        // Public service operations

        public BorrowBookResponse BorrowBook(BorrowBookRequest borrowBookRequest)
        {
            try
            {
                return _booksBorrowOperations.BorrowBook(borrowBookRequest);
            }
            catch (Exception exception)
            {
                // TODO: Log relevant message...

                return new BorrowBookResponse(false, exception.Message);
            }
        }

        public ReturnBookResponse ReturnBook(ReturnBookRequest returnBookRequest)
        {
            try
            {
                return _booksBorrowOperations.ReturnBook(returnBookRequest);
            }
            catch (Exception exception)
            {
                // TODO: Log relevant message...

                return new ReturnBookResponse(false, exception.Message);
            }
        }

        public CreateBookResponse CreateBook(CreateBookRequest createBookRequest)
        {
            try
            {
                return _booksCRUDOperations.CreateBook(createBookRequest);
            }
            catch (Exception exception)
            {
                // TODO: Log relevant message...

                return new CreateBookResponse(false, exception.Message);
            }
        }
    }
}


Description:

-        The BooksServices class contains the required ‘Books Operations’, such as: BorrowBook, ReturnBook, CreateBook, ReadBook, etc.

-        In addition, the BooksServices class contains the following interfaces which represent the abstractions of all the operations (services) that it needs: IBooksBorrowOperations, IBooksCRUDOperations.

-        I’ve created a dependency-injection constructor that allows users to inject the relevant interfaces as desired, which means that we’ve eliminated any dependency to the lower-level layer that implements those interfaces, this is also very useful for testing, using mocking.

-        The BooksServices class’s constructor also provides a default implementation for those interfaces.



2.    Books Operations Layer


IBooksBorrowOperations.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;

namespace DependencyInversionPrinciple_Example.Operations.Books
{
    public interface IBooksBorrowOperations
    {
        BorrowBookResponse BorrowBook(BorrowBookRequest borrowBookRequest);

        ReturnBookResponse ReturnBook(ReturnBookRequest returnBookRequest);
    }
}


IBooksCRUDOperations.cs
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;

namespace DependencyInversionPrinciple_Example.Operations.Books
{
    public interface IBooksCRUDOperations
    {
        CreateBookResponse CreateBook(CreateBookRequest createBookRequest);


        // TODO: Need to implement...

        //UpdateBookResponse UpdateBook(UpdateBookRequest updateBookRequest);
        //ReadBookResponse ReadBook(ReadBookRequest readBookRequest);
        //DeleteBookResponse DeleteBook(DeleteBookRequest deleteBookRequest);
    }
}


BooksOperationsManager.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;
using DependencyInversionPrinciple_Example.Tasks.Books;
using DependencyInversionPrinciple_Example.Validation.Books;

namespace DependencyInversionPrinciple_Example.Operations.Books
{
    public class BooksOperationsManager : IBooksBorrowOperations, IBooksCRUDOperations
    {
        // Private readonly variables

        private readonly IBooksBorrowOperationsValidator _booksBorrowOperationsValidator;
        private readonly IBooksCrudOperationsValidator _booksCrudOperationsValidator;


        // Constructors

        public BooksOperationsManager(
                    IBooksBorrowOperationsValidator booksBorrowOperationsValidator = null,
                    IBooksCrudOperationsValidator booksCrudOperationsValidator = null)
        {
            // Setting default IBooksBorrowOperationsValidator
            // implementation, in case wasn't provided.
            _booksBorrowOperationsValidator =
                booksBorrowOperationsValidator ?? new BooksBorrowOperationsValidator();

            // Setting default IBooksCrudOperationsValidator
            // implementation, in case wasn't provided.
            _booksCrudOperationsValidator =
                booksCrudOperationsValidator ?? new BooksCrudOperationsValidator();
        }


        // Public borrow operations

        public BorrowBookResponse BorrowBook(BorrowBookRequest borrowBookRequest)
        {
            // Validation

            if (!_booksBorrowOperationsValidator.ValidateBorrowBookRequest(borrowBookRequest))
            {
                // TODO: Log relevant message...

                return new BorrowBookResponse(false, "Request is Not valid");
            }

            // Borrow Book Implementation

            // Remark:
            //  We could also add another layer to encapsulate the
            //  DAL layer for DB operations, using a proper interface.
           
            //  In the current implementation I used a concrete class, for testing only!
            return new BorrowBookTask().Execute(borrowBookRequest) as BorrowBookResponse;
        }

        public ReturnBookResponse ReturnBook(ReturnBookRequest returnBookRequest)
        {
            // Validation

            if(!_booksBorrowOperationsValidator.ValidateReturnBookRequest(returnBookRequest))
            {
                // TODO: Log relevant message...

                return new ReturnBookResponse(false, "Request is Not valid");
            }

            // TODO: Return Book Implementation...

            return new ReturnBookResponse(true);
        }


        // Public CRUD operations

        public CreateBookResponse CreateBook(CreateBookRequest createBookRequest)
        {
            // Validation

            if (!_booksCrudOperationsValidator.ValidateCreateBookRequest(createBookRequest))
            {
                // TODO: Log relevant message...

                return new CreateBookResponse(false, "Request is Not valid");
            }

            // TODO: Create Book Implementation...

            return new CreateBookResponse(true);
        }
    }
}


Description:

-        The BooksOperationsManager class implements both IBooksBorrowOperations and IBooksCRUDOperations interfaces.

-        In addition, the BooksOperationsManager class contains the following interfaces which represent the abstractions of all the operations (services) that it needs: IBooksBorrowOperationsValidator, IBooksCrudOperationsValidator.

-        I’ve created a dependency-injection constructor that allows users to inject the relevant interfaces as desired, which means that we’ve eliminated any dependency to the lower-level layer that implements those interfaces, this is also very useful for testing, using mocking.

-        The BooksOperationsManager class’s constructor also provides a default implementation for those interfaces.



3.    Books Validation Layer


IBooksBorrowOperationsValidator.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;

namespace DependencyInversionPrinciple_Example.Validation.Books
{
    public interface IBooksBorrowOperationsValidator
    {
        bool ValidateBorrowBookRequest(BorrowBookRequest borrowBookRequest);

        bool ValidateReturnBookRequest(ReturnBookRequest returnBookRequest);
    }
}


BooksBorrowOperationsValidator.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;

namespace DependencyInversionPrinciple_Example.Validation.Books
{
    public class BooksBorrowOperationsValidator : IBooksBorrowOperationsValidator
    {
        public bool ValidateBorrowBookRequest(BorrowBookRequest borrowBookRequest)
        {
            return borrowBookRequest?.Id > 0;
        }

        public bool ValidateReturnBookRequest(ReturnBookRequest returnBookRequest)
        {
            return returnBookRequest?.Id > 0;
        }
    }
}


IBooksCrudOperationsValidator.cs
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;

namespace DependencyInversionPrinciple_Example.Validation.Books
{
    public interface IBooksCrudOperationsValidator
    {
        bool ValidateCreateBookRequest(CreateBookRequest createBookRequest);
    }
}


BooksCrudOperationsValidator.cs
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;
using System;

namespace DependencyInversionPrinciple_Example.Validation.Books
{
    public class BooksCrudOperationsValidator : IBooksCrudOperationsValidator
    {
        public bool ValidateCreateBookRequest(CreateBookRequest createBookRequest)
        {
            // TODO: Need to implement...

            return true; // For testing
        }
    }
}


Description:

-        In this layer I’ve declared 2 classes for the validation interfaces for the upper-level layer (Books Operations Layer).

-        Class BooksBorrowOperationsValidator that implements IBooksBorrowOperationsValidator.

-        Class BooksCrudOperationsValidator that implements IBooksCrudOperationsValidator.



4.     Testing


TestBooksOperations.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;
using DependencyInversionPrinciple_Example.Operations.Books;
using DependencyInversionPrinciple_Example.Services.Books;
using DependencyInversionPrinciple_Example.Validation.Books;
using Moq;
using NUnit.Framework;

namespace DIP_Testing
{
    [TestFixture]
    public class TestBooksOperations
    {
        #region Test Methods

        [Test]
        public void TestBorrowBookOperation()
        {
            // 1. Create a Mock object.
            var booksOperationsManagerMock = Create_BooksOperationsManager_Mock();

            // 2. Test the 'BorrowBook()' method using the mock objects.
            var response = new BooksServices(booksOperationsManagerMock.Object)
                                        .BorrowBook(It.IsAny<BorrowBookRequest>());

            // 3. Assert
            Assert.IsTrue(response.IsSuccessful);
        }

        #endregion Test Methods

        #region Private Helper Methods

        // Create an 'IBooksBorrowOperationsValidator' mock object, and setting
        // up its 'ValidateBorrowBookRequest()' method return-value to true.
        private Mock<IBooksBorrowOperationsValidator>
                               Create_IBooksBorrowOperationsValidator_Mock()
        {
            var booksBorrowOperationsValidatorMock = new Mock<IBooksBorrowOperationsValidator>();

            booksBorrowOperationsValidatorMock
                        .Setup(x => x.ValidateBorrowBookRequest(It.IsAny<BorrowBookRequest>()))
                        .Returns(true);

            return booksBorrowOperationsValidatorMock;
        }

        // Creating a 'BooksOperationsManager' Mock object and sending its
        // constructor the 'IBooksBorrowOperationsValidator' mock object.
        private Mock<BooksOperationsManager> Create_BooksOperationsManager_Mock()
        {
            return
                new Mock<BooksOperationsManager>(
                                Create_IBooksBorrowOperationsValidator_Mock().Object, null);
        }

        #endregion Private Helper Methods
    }
}


Description:

-        I’ve added a simple test only to demonstrate how to invoke this implementation using Mock Objects, and by using the benefits of the loosed-couple code we’ve designed.



Mocking using Moq

In this example I’ve used Moq, which is (in short) an open source mocking-framework for testing .Net applications.

With Moq we could isolate and decouple the tested code from its (real) dependencies, by using fake dependencies.

This way we could easily identify the ‘problematic code’, since we are removing any other obstacle that could otherwise interfere with the tests.

In addition, Moq also provides LINQ capabilities which makes it easy to write and maintain.

This package could be download from: NuGet Must Haves –  http://nugetmusthaves.com/Package/Moq




                                                          
IoC Containers, DI Containers & Service Locators


When discussing the DIP and dependency-injection techniques, it’s only appropriate to mention Inversion of Control (IoC) Containers also known as Dependency Injection (DI) Containers, and Service Locators.


In short…

An IoC (or DI) Container is a generic implementation of the Dependency Inversion Principle, which describes the architecture to decouple objects from their services (other objects they use), in order to achieve better flexible, testable and scalable designs (as described in this article).

The term “inversion of control” refers to the fact that the consumed-class is no longer responsible to instantiate its composite services, this responsibility was provided to an external container.

The Dependency Injection Pattern is a private case of the IoC pattern. We could use interfaces to create abstraction of the services we need, and use dependency-injection techniques to decuple the class from the desired services.

The Service Locator Pattern is also a private case of the IoC pattern. We could use a Service Locator Object in our implementation, which would be responsible to locate the desired service implementation at run-time. In this pattern we don’t have to use dependency-injection techniques, although we could integrate these 2 patterns.


There are several IoC Containers we could use. Few of the most popular are:

-        Ninject
-        Unity
-        Spring.NET
-        CastleWindsor
-        StructureMap
-        Autofac



Example:

In this simple example I used the Ninject implementation.

I’ve added a Ninject Module to register interfaces with their concrete implementation.
This means that every time we’d like to use an interface we’ll get the proper concrete class.


BooksNinjectModule.cs
using DependencyInversionPrinciple_Example.Operations.Books;
using DependencyInversionPrinciple_Example.Validation.Books;
using Ninject.Modules;

namespace DependencyInversionPrinciple_Example.NinjectModules
{
    public class BooksNinjectModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IBooksBorrowOperations>().To<BooksOperationsManager>();
            Bind<IBooksCRUDOperations>().To<BooksOperationsManager>();

            Bind<IBooksBorrowOperationsValidator>().To<BooksBorrowOperationsValidator>();
            Bind<IBooksCrudOperationsValidator>().To<BooksCrudOperationsValidator>();
        }
    }
}


I’ve modified the BooksOperationsManager class to use BooksNinjectModule class to get the default implementation as was pre-registered.

Remark:
We could implement this solution in several different ways.

For example, to create this class without a dependency-injection constructor, and to retrieve the proper concrete class using the Ninject StandardKernel as shown below.

Alternately, we could also use a Service Locator implementation to auto-inject these dependencies, which would occur on run-time. (not shown below)


Please see the following code fragment:

BooksOperationsManager.cs
using DependencyInversionPrinciple_Example.Documents.Books.BorrowOperations;
using DependencyInversionPrinciple_Example.Documents.Books.CrudOperations;
using DependencyInversionPrinciple_Example.NinjectModules;
using DependencyInversionPrinciple_Example.Tasks.Books;
using DependencyInversionPrinciple_Example.Validation.Books;
using Ninject;

namespace DependencyInversionPrinciple_Example.Operations.Books
{
    public class BooksOperationsManager : IBooksBorrowOperations, IBooksCRUDOperations
    {
        // Private readonly variables

        private readonly IBooksBorrowOperationsValidator _booksBorrowOperationsValidator;
        private readonly IBooksCrudOperationsValidator _booksCrudOperationsValidator;


        // Constructors

        public BooksOperationsManager(
                    IBooksBorrowOperationsValidator booksBorrowOperationsValidator = null,
                    IBooksCrudOperationsValidator booksCrudOperationsValidator = null)
        {
            IKernel kernel = new StandardKernel(new BooksNinjectModule());

            // Setting default IBooksBorrowOperationsValidator
            // implementation, in case wasn't provided.
            //_booksBorrowOperationsValidator =
            //    booksBorrowOperationsValidator ?? new BooksBorrowOperationsValidator();

            _booksBorrowOperationsValidator = booksBorrowOperationsValidator ??
                kernel.Get<IBooksBorrowOperationsValidator>();

            // Setting default IBooksCrudOperationsValidator
            // implementation, in case wasn't provided.
            //_booksCrudOperationsValidator =
            //    booksCrudOperationsValidator ?? new BooksCrudOperationsValidator();

            _booksCrudOperationsValidator = booksCrudOperationsValidator ??
                kernel.Get<IBooksCrudOperationsValidator>();
        }
        

     . . .
  
    }
}





Summary

We managed to examine a simple case study that emphasizes the importance of the Dependency Inversion Principle, and its effect on our codebase with respect to software design smells.

I would say that it’s quite easy to adhere to this principle and to use dependency-injection techniques in our code wherever needed.

As a best practice, we should use IoC/DI Containers along with Dependency Injection & Service Locators implementations, in order to achieve better robust and loosely-coupled solutions.

Furthermore, the DIP highlights the ‘debatable’ Favor Composition Over Inheritance key-design principle (a.k.a composite reuse principle), which (in short) states that we should use composition in our classes instead of inheritance, since such classes are much more flexible, scalable and tolerance to changes, especially with abstraction and dependency-injection capabilities.

If I were to extreme, I would strive to design every feature in our codebase to depend upon abstraction, in order to provide a better robust solution, especially when most of the S.O.L.I.D principles guide us to abstraction.

This would also provide better Unit Testing support, using mocking techniques.

However, I would stress the notion that, as with the rest of the S.O.L.I.D principles, we should always consider different development elements, and in a common-sense manner, and not follow any instruction blindly, even the Object Oriented Design Principles.

Furthermore, I would say that additional important patterns were concluded from the Dependency Inversion Principle, that emphasized the loosely-coupled designs, patterns such as: The Hollywood Principle (“Don’t Call Us, We’ll Call You.”), The Law of Demeter - LoD (a.k.a The Principle of Least Knowledge) and others.

So, to conclude I would say that the DIP is very simple to use, we should examine where to use it in our designs and is best used with the rest of the design principles, in order to provide better maintainable, testable & scalable solutions, and naturally to prevent software design smells.


---
This article was the last of 7 articles describing & illustrating the Object Oriented Design Principles, a.k.a S.O.L.I.D Principles, which aspired to reveal the beauty & science of software design.

As stated in the first article: SOLID– The Principles of Object Oriented Design these principles were first gathered and presented by Robert Cecil Martin (a.k.a Uncle Bob).

Hope you enjoyed, and more importantly apply in your designs.

  


The End

Hope you enjoyed!
Appreciate your comments…

Yonatan Fedaeli