Thursday, 15 October 2015

Inversion of control

Inversion of control is a design pattern that eliminates dependencies by supplying services and delegates to classes instead of defining them in that class. What does that mean? Well the S in SOLID stands for Single responsibility which is one of the goals of modern software development: low coupling and high cohesion which basically means we want our classes to be very specific and independent of each other, if we have an Employee class, that class should only know things about being an employee, not about printing an employee or creating an employee report or anything to do with an employees hours.

before we get started, open your .csproj file, it should look like the following:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>


replace it with 

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <UseWindowsForms>true</UseWindowsForms>
    <TargetFramework>net6.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>


notice how we the 'UseWindowsForms' node and set it to true, and we change the 'TargetFramework' to 'net6.0-windows', these changes will let us use the windows forms ui elements inside our 'Console' app, we'll only be interested in the Message box.

Let's stick to our idea of an employee inheriting from a person, but this time let's create a report service first, our report service will have the responsibility of 'printing' the employee or person info, this is a bit contrived, but stick with me.


class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName { get { return $"{FirstName} {LastName}"; } }
    public Person() {
        this.FirstName = "";
        this.LastName = "";
    }
    public Person(string FirstName, string LastName) : this()
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
}

class Employee : Person {
    static int runningID = 0;
    public int Id { get; private set; } = runningID++;

    IReportService<Employee> ReportService;

    public Employee(string fn, string ln, IReportService<Employee> ReportService)
        : base(fn, ln) {
        this.ReportService = ReportService;
    }

    public void Print() {
        if (ReportService == null)
            throw new NullReferenceException("No Report service ");

        ReportService.Output(this);
    }
}


above we are using our standard Employee: Person implementation however with one exception, we pass into the constructor of the Employee an IReportService which we use in the print method, now let's take a look at our IReportService interface.


interface IReportService<T> {
    void Output(T emp);
}


pretty simple, it defines one method, now let's create not one, but two implementations of our interface.


class ConsoleReportService : IReportService<Employee>
{
    public void Output(Employee emp)
    {
        Console.WriteLine(emp.FullName);
    }
}


the first uses the console to output our employee's full name. For the second we're going to use a message box.


class MessageReportService : IReportService<Employee>
{
    public void Output(Employee emp)
    {
        MessageBox.Show(emp.FullName);
    }
}


with those complete let's take a look at our main


class Program
{
    static void Main(string[] args)
    {

        var emp = new Employee("Pawel", "Ciucias", new ConsoleReportService());
        emp.Print();

        var emp2 = new Employee("Tomek", "Ciucias", new MessageReportService());
        emp2.Print();
    }
}


We can see that our employee class really has no clue how our ReportService works, it just knows that there is one and that it has a print method. we can have multiple variations of our Report service and just pass whichever one is appropriate at the time.

to make it easer to copy and paste here's the full code.

namespace pav.ioc;

interface IReportService<T> {
    void Output(T emp);
}

class ConsoleReportService : IReportService<Employee>
{
    public void Output(Employee emp)
    {
        Console.WriteLine(emp.FullName);
    }
}

class MessageReportService : IReportService<Employee>
{
    public void Output(Employee emp)
    {
        MessageBox.Show(emp.FullName);
    }
}

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName { get { return $"{FirstName} {LastName}"; } }
    public Person() {
        this.FirstName = "";
        this.LastName = "";
    }
    public Person(string FirstName, string LastName) : this()
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
}

class Employee : Person {
    static int runningID = 0;
    public int Id { get; private set; } = runningID++;

    IReportService<Employee> ReportService;

    public Employee(string fn, string ln, IReportService<Employee> ReportService)
        : base(fn, ln) {
        this.ReportService = ReportService;
    }

    public void Print() {
        if (ReportService == null)
            throw new NullReferenceException("No Report service ");

        ReportService.Output(this);
    }
}

class Program
{
    static void Main(string[] args)
    {

        var emp = new Employee("Pawel", "Ciucias", new ConsoleReportService());
        emp.Print();

        var emp2 = new Employee("Tomek", "Ciucias", new MessageReportService());
        emp2.Print();
    }
}