ES as Exercises of Dependency Inversion Principle

By Karol Bocian | March 11, 2020

Dependency inversion principle

High-level modules should not depend on low-level modules, but the dependence should result from abstraction.

How to use DIP in practice?

We should make many abstract creatures. Every class should implement interface or inherits abstract class. At the beginning of making task, we can write only interfaces.

Helpfully to achieve the DIP principle, are these rules:

  • Every field in class is a reference to an abstract type.
  • All classes inherit or implements abstraction.
  • No class of posterity overloads the base class methods.
  • To set fields we use factory pattern or dependency injection.

Example

We have a class, which showing report (ReportPresenter). It takes data from database.

class Program
   {
       static void Main(string[] args)
       {
 
           var reportImporter = new ReportDatabaseImporter();
           var reportPresenter = new ReportPresenter(reportImporter);
           reportPresenter.ShowReport();
       }
   }
 
   internal class ReportPresenter
   {
 
       private readonly ReportDatabaseImporter importer;
 
 
       public ReportPresenter(ReportDatabaseImporter importer)
       {
           this.importer = importer;
       }
 
       public void ShowReport()
       {
           Console.WriteLine(importer.GetReportData());
       }
   }
 
 
 
   internal class ReportDatabaseImporter
 
   {
       public string GetReportData()
 
       {
           return "Report from Database";
       }
   }

After a few weeks, we got request to show some data from Excel file too. Unfortunately, we can’t do it easily. We have to change ReportPresenter. This solution allows us adding a new report source very easily:

using System;
 
namespace SRP.Bad
{
    class Program
    {
        static void Main(string[] args)
        {
            var reportImporter = new ReportDatabaseImporter();
            var reportPresenter = new ReportPresenter(reportImporter);
            reportPresenter.ShowReport();
 
 
 
            var reportImporter2 = new ReportExcelImporter();
            var reportPresenter2 = new ReportPresenter(reportImporter2);
            reportPresenter2.ShowReport();
        }
    }
 
    internal class ReportPresenter : IReportPresenter
    {
        private readonly IReportImporter importer;
 
        public ReportPresenter(IReportImporter importer)
        {
            this.importer = importer;
        }
        public void ShowReport()
        {
            var reportData = importer.GetReportData();
            Console.WriteLine(reportData.GetContent());
        }
    }
 
    internal class ReportDatabaseImporter : IReportImporter
    {
        public IReportData GetReportData()
        {
            return new ReportData("Report from Database");
        }
    }
    internal class ReportExcelImporter : IReportImporter
    {
        public IReportData GetReportData()
        {
            return new ReportData("Report from Excel");
        }
    }
    internal class ReportData : IReportData
    {
        private readonly string content;
 
        public ReportData(string content)
        {
            this.content = content;
        }
        public string GetContent()
        {
            return content;
        }
    }
    internal interface IReportData
    {
        string GetContent();
    }
    internal interface IReportImporter
    {
        IReportData GetReportData();
    }
 
    internal interface IReportPresenter
    {
        void ShowReport();
    }
}

We have there many interfaces. They help us to develop application and testing it. In tests, we can put a mock at an interface.

All posts from mini project: Learn SOLID and OOP principles:

Sources

Main image

Materials

  • Clean Architecture — Robert C. Martin