C# IoC Learning Notes

1. Introduction

IoC-Invertion of Control, or Control Inversion, is a programming idea.

Preliminary understanding of several concepts:

Dependency: is the association, meaning that one class depends on another class.

Dependency on Inverted Principle (DIP): One of the six principles of design mode is a software architecture design principle.

Control Inversion (IoC): A software design principle in which the dependency of the upper layer on the lower layer (i.e., the acquisition of the lower module) is handed over to a third party.

Dependent Injection (DI): A way to implement IoC.

IoC container: A framework that relies on injection to map dependencies and manage the creation and lifecycle of objects.

2. Dependency

Dependency is a connection, and using it anywhere is a dependency. Here's a simple example:

    class Program
    {
        class BMW
        {
            public string Show()
            {
                return "BMW";
            }
        }
        class ChinesePeople
        {
            private BMW bmw = new BMW();
            public void Run()
            {
                Console.WriteLine($"Open today{bmw.Show()}go to work");
            }
        }

        static void Main(string[] args)
        {
            ChinesePeople people = new ChinesePeople();
            BMW bmw = new BMW();
            people.Run();
            Console.Read();
        }
    }

The Chinese above drive a BMW to work. The clients use Chinese people and BMW cars. The Chinese people use BMW cars. We can find three dependencies among them:

Client relies on ChinesePeople;

Client depends on object BMW;

ChinesePeople relies on object BMW;

3. Principle of Dependency Inversion

There's a new demand coming along these days. Chinese people not only drive BMW to work, but also run to work. If we do this in a way that directly depends on it, we need to modify the ChinesePeople class so that it can implement an overloaded method, Run(), with a parameter of BMW.Obviously this is not a good design. We can't modify the ChinesePeople class every time we add a new car (that is, modify the lower module). That's too much trouble.

To start with, a simple analysis shows that a coupling relationship is a dependency relationship. If the dependency is very heavy and affects the whole body, it will be difficult to maintain expansion. The fewer the coupling relationships, the more stable the system will be and therefore less dependent.

Definition:

A. High-level modules should not depend on low-level modules, but on abstraction.

B. Abstraction should not depend on detail, but on abstraction.

In this diagram, we find that high-level module defines interfaces and will not depend directly on the lower-level modules, which are responsible for implementing the interfaces defined by the higher-level modules. Here's an example:

    class Program
    {
        interface ICar
        {
            string Show();
        }

        class BMW : ICar
        {
            public string Show()
            {
                return "BMW";
            }
        }

        class BenZ : ICar
        {
            public string Show()
            {
                return "Benz";
            }
        }

        interface IPeople
        {
            void Run(ICar car);
        }

        class ChinesePeople : IPeople
        {
            public void Run(ICar car)
            {
                Console.WriteLine($"Open today{car.Show()}go to work");
            }
        }

        static void Main(string[] args)
        {
            ICar bmw = new BMW();
            ICar benz = new BenZ();
            IPeople people = new ChinesePeople();
            people.Run(bmw);
            people.Run(benz);
            Console.Read();
        }
    }

The results are as follows:

Analysis: In the code above, the ChinesePeople class no longer relies on a specific car, but on the abstraction of the car, which makes it possible for Chinese people to drive to work regardless of their brand, and there is no need to modify the ChinesePeople class.Think about whether this is good or not, and we can conclude that the upper layer is no longer dependent on detail, but is better oriented to interfaces than implementation, because abstraction is more stable than detail.

4. Control Reversal

In the example above, we isolate a specific person from a specific car, who is only related to the interface of the car.But the specific object in the Main method in the Program is written to death and the control becomes smaller. When I want to modify Americans driving Ford to work, I have to not modify the code. How can I transfer the control?

Here's a simple example (add a System.Configuration reference first):

    interface ICar
    {
        string Show();
    }
ICar.cs
    interface IPeople
    {
        void Run(ICar car);
    }
IPeople.cs
    class BMW : ICar
    {
        public string Show()
        {
            return "BMW";
        }
    }
BMW.cs
    class ChinesePeople : IPeople
    {
        public void Run(ICar car)
        {
            Console.WriteLine($"Open today{car.Show()}go to work");
        }
    }
ChinesePeople.cs
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <appSettings>
        <add key="People" value="LinkTo.Test.ConsoleIoC.ChinesePeople,LinkTo.Test.ConsoleIoC"/>
        <add key="Car" value="LinkTo.Test.ConsoleIoC.BMW,LinkTo.Test.ConsoleIoC"/>
    </appSettings>
</configuration>
App.config
    class Program
    {
        static void Main(string[] args)
        {
            #region reflex+Profile implementation Ioc
            string people = ConfigurationManager.AppSettings["People"];
            string car = ConfigurationManager.AppSettings["Car"];
            Assembly assemblyPeople = Assembly.Load(people.Split(',')[1]);
            Assembly assemblyCar = Assembly.Load(car.Split(',')[1]);
            Type typePeople = assemblyPeople.GetType(people.Split(',')[0]);
            Type typeCar = assemblyPeople.GetType(car.Split(',')[0]);
            IPeople ipeople = (IPeople)Activator.CreateInstance(typePeople);
            ICar icar = (ICar)Activator.CreateInstance(typeCar);
            ipeople.Run(icar);
            Console.Read();
            #endregion
        }
    }
Program.cs

In the code above, we use Reflection+Profile to transfer control of object creation to Profile, which is called Control Reverse.

Analysis: Control reversal gives control of object creation to a third party, which can be an IoC container, which is equivalent to a simple factory.The factory gives us everything we want, so the dependencies change. They (people and cars) depend on the IoC container to establish their dependencies.(Dependent objects are no longer directly acquired through new)

The results are as follows:

5. Dependent Injection

When we talk about reversing control above, we understand that it is our purpose to transfer control.Profile+Reflection is an implementation, while Dependent Injection provides an idea, or a means of implementing IoC.

Dependency Injection transfers the creation and binding of objects to the outside of the dependent objects. What are the general ways to do this?

Method 1: Constructor injection

    class ChinesePeopleConstructor
    {
        private readonly ICar _car;

        //Dependent Injection: Constructor Injection
        public ChinesePeopleConstructor(ICar car)
        {
            _car = car;
        }

        public void Run()
        {
            Console.WriteLine($"Open today{_car.Show()}go to work");
        }
    }
ChinesePeopleConstructor.cs
    class Program
    {
        static void Main(string[] args)
        {
            #region Dependent Injection: Constructor Injection
            ICar bmw = new BMW();
            ChinesePeopleConstructor people = new ChinesePeopleConstructor(bmw);
            people.Run();
            Console.Read();
            #endregion
        }
    }
Program.cs

Method 2: Attribute injection

    class ChinesePeopleProperty
    {
        //Dependent Injection: Attribute Injection
        public ICar Car { get; set; }

        public void Run()
        {
            Console.WriteLine($"Open today{Car.Show()}go to work");
        }
    }
ChinesePeopleProperty.cs
    class Program
    {
        static void Main(string[] args)
        {
            #region Dependent Injection: Attribute Injection
            ICar bmw = new BMW();
            ChinesePeopleProperty people = new ChinesePeopleProperty
            {
                Car = bmw
            };
            people.Run();
            Console.Read();
            #endregion
        }
    }
Program.cs

Method 3: Interface injection

    interface IDependent
    {
        void SetDependent(ICar icar);
    }
IDependent.cs
    class ChinesePeopleInterface : IDependent
    {
        private ICar _car;

        //Dependent Injection: Interface Injection
        public void SetDependent(ICar car)
        {
            _car = car;
        }

        public void Run()
        {
            Console.WriteLine($"Open today{_car.Show()}go to work");
        }
    }
ChinesePeopleInterface.cs
    class Program
    {
        static void Main(string[] args)
        {
            #region Dependent Injection: Interface Injection
            ICar bmw = new BMW();
            ChinesePeopleInterface people = new ChinesePeopleInterface();
            people.SetDependent(bmw);
            people.Run();
            Console.Read();
            #endregion
        }
    }
Program.cs

6. IoC Container

The IoC container is a DI framework with the following main functions:

A. Create and inject dependent objects dynamically;

B. Manage object life cycle;

C. Mapping dependencies;

Common IoC containers: Spring.NET, Castle Windsor, Ninject, Autofac, Unity.

6.1, Unity container usage

In the last article C# AOP Learning Notes In EntLib\PIAB Unity for AOP with Configuration, the Unity container has been used to implement IoC. Let's look at the configuration file again:

container would look like this if only IoC needed no AOP:

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
  </configSections>
  <unity>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
    <containers>
      <container name="IoCContainer">
        <!--Registration Matching Rule: Complete type name before, followed by dll Name.-->
        <register type="LinkTo.Test.ConsoleAop.UnityConfigAOP.IUserProcessor,LinkTo.Test.ConsoleAop" mapTo="LinkTo.Test.ConsoleAop.UnityConfigAOP.UserProcessor,LinkTo.Test.ConsoleAop"></register>
      </container>
    </containers>
  </unity>
</configuration>

The registration matching rules for IoC show that the complete type name is preceded by the DLL where it is located.This is more powerful. If a system has extended functionality or personality requirements, just configure and use the new dll, and the original system does not need to change the code.In this way, in addition to being in good compliance with the Open and Close Principles, it is a powerful tool for building a configurable and scalable system.(

 

Reference from:

    https://www.cnblogs.com/jdzhang/p/7104351.html

Recommended posts:

    Design Three-tier Architecture Based on Interface

Tags: C# Unity Attribute Programming less

Posted on Thu, 19 Mar 2020 22:58:05 -0400 by marco839