IoC mode (dependency, dependency inversion, dependency injection, control inversion)

Posted by sportryd on Tue, 25 Jan 2022 13:37:34 +0100

1. Dependence

Dependency means connection. If it is used somewhere, it means dependency. A system cannot completely avoid dependency. If your class or module doesn't use it in the project, Congratulations, you can eliminate it or exclude it from the project, because there is no place to rely on it. Here is a simple example:

 /// <summary>
    /// Users play media files
    /// </summary>
    public class OperationMain
    {
        public void PlayMedia()
        {
            MediaFile _mtype = new MediaFile();
            Player _player = new Player();

            _player.Play(_mtype);
        }
    }
    /// <summary>
    /// player
    /// </summary>
    public class Player
    {
        public void Play(MediaFile file)
        {
            Console.WriteLine(file.FilePath);
        }
    }
    /// <summary>
    /// Media files
    /// </summary>
    public class MediaFile
    {
        public string FilePath { get; set; }
    }

The above is a simple example of a user playing a file with a player. The user's operation is the PlayMedia method in the OperationMain class. Open a player and select a file to play. First look at the dependencies between them. You can easily find that there are three dependencies

    1. Player depends on MediaFile
    2. OperationMain depends on Player
    3. OperationMain depends on MediaFile

    2. Dependency inversion

    As the demand increases, we need to use different players to play different files. We need to abstract them and reduce coupling.

    Coupling relationship is a dependency relationship. If the dependency relationship is quite complex, it will affect the whole body and is difficult to maintain; The fewer dependencies, the lower the coupling relationship and the more stable the system is, so we should reduce dependencies.

    Fortunately, master Robert Martin proposed the principle of object-oriented design -- the principle of dependency inversion:

    • A. The upper modules should not depend on the lower modules. They all depend on an abstraction.  
    • B. Abstract cannot depend on concrete, concrete depends on abstract.

    Understanding: A. the upper layer is the user and the lower layer is the user. As A result, the upper layer depends on the lower layer. If the lower layer changes, it will naturally affect the upper layer, resulting in system instability and even affect the whole body. So how to reduce dependence? That is, both the upper and lower levels rely on another abstraction. This abstraction is relatively stable, and the whole is relatively stable.

    B. In object-oriented programming, abstraction or excuse oriented programming is generally stable. The concrete realization of abstraction must rely on abstraction. Abstraction should not rely on other concrete, but on abstraction.

     

    In the above player example, we have found the dependency. Now we need to optimize according to the dependency inversion principle.

    According to the following principles:

    • Player depends on MediaFile. It's easy to handle. Let both player and MediaFile depend on an abstract IMediaFile
    • OperationMain depends on Player, which is easy to handle. Let both OperationMain and Player depend on an abstract IPlayer
    • OperationMain depends on MediaFile, which is easy to handle. Let both OperationMain and MediaFile rely on an abstract IMediaFile
    • IPlayer cannot rely on specific MediaFile, but should rely on the abstract IMediaFile of specific MediaFile

    The structure is very simple, so the code is roughly as follows:

  1.  /// <summary>
        /// Users play media files
        /// </summary>
        public class OperationMain
        {
            public void PlayMedia()
            {
                IMediaFile _mtype = new MediaFile();
                IPlayer _player = new Player();
    
                _player.Play(_mtype);
            }
        }
        /// <summary>
        /// player
        /// </summary>
        public interface IPlayer
        {
            void Play(IMediaFile file);
        }
        /// <summary>
        /// Default player
        /// </summary>
        public class Player : IPlayer
        {
            public void Play(IMediaFile file)
            {
                Console.WriteLine(file.FilePath);
            }
        }
        /// <summary>
        /// Media files
        /// </summary>
        public interface IMediaFile
        {
            string FilePath { get; set; }
        }
        /// <summary>
        /// Default media file
        /// </summary>
        public class MediaFile : IMediaFile
        {
            public string FilePath { get; set; }
        }

    The above code has been abstracted. It can be seen that the purpose is to reduce the dependency, but it seems that the dependency relationship has increased. For example, the user PlayMedia method also increases the dependency interface and specific implementation. However, the interface is stable and can not be considered. The specific implementation is changed. This dependency is still necessary and the file should be played, Specific players and specific files must be used.

    3. Control reversal (IoC)

    In real life, a specific player has nothing to do with a specific media file. If you give it an Mp3 file, it can play, and if you give it an Mp4 file, it can also play. If you delete your media file, the player is still there. The specific player and file are all controlled by our users.

    In the above example, the isolation is basically realized. The specific player is isolated from the specific media. The specific player is only related to the media interface and player interface. But the specific object in PlayMedia's method is dead, and the control right is very small. If I want to play Baidu video, I want to change a music, and I can only change the code again, how can the control be transferred?

    We can create it through reflection and write the specific file name in the configuration file. At this time, the client code does not need to change. Just change the configuration file, and the stability is improved, as follows:

  2.  public void PlayMedia()
            {
                IMediaFile _mtype = Assembly.Load(ConfigurationManager.AppSettings["AssemName"]).CreateInstance(ConfigurationManager.AppSettings["MediaName"]);
                IPlayer _player = Assembly.Load(ConfigurationManager.AppSettings["AssemName"]).CreateInstance(ConfigurationManager.AppSettings["PlayerName"]);
    
                _player.Play(_mtype);
            }

    The specific object is controlled by the configuration file. The control of the specific object is handed over to the configuration file, which is also commonly known as control reversal.

    Inversion of Control IoC is the abbreviation of Inversion of Control, which means that the control right of an object is transferred to a third party. For example, if the transfer is handed over to the IoC container, it is a creation factory. It will give you any object you want. With the IoC container, the dependency will change, and the original dependency will disappear. They all depend on the IoC container, The relationship between them is established through the IoC container.

    4. Dependency injection (DI)

    The above mentioned control reversal is an idea and concept, but it should also be implemented specifically. The above configuration file is also an implementation method. Dependency injection puts forward specific ideas.

    Dependency Injection DI is the abbreviation of Dependency Injection, which puts forward "which things have their control reversed and transferred?", It also gives the answer: "the creation and acquisition of dependent objects are reversed".

    The so-called dependency injection means that the IoC container dynamically injects certain dependencies into objects during operation.

    In the above example, where dependent injection and dependent objects need to obtain instances, that is, the PlayMedia method, requires the specific object of IPlayer and the specific object of IMediaFile. Start here when you find the place. In order to flexibly control these two objects, you must be able to control the instantiation of the two objects from the outside. It is necessary to provide external operations, which can be attributes, It can be a method or a constructor. In short, it can be controlled elsewhere. Unity injection will be used below. Constructor injection is used. The code is as follows:

  3.  /// <summary>
        /// Users play media files
        /// </summary>
        public class OperationMain
        {
            IMediaFile _mtype;
            IPlayer _player;
    
            public OperationMain(IPlayer player, IMediaFile mtype)
            {
                _player = player;
                _mtype = mtype;
            }
    
            public void PlayMedia()
            {
                _player.Play(_mtype);
            }
        }
        /// <summary>
        /// player
        /// </summary>
        public interface IPlayer
        {
            void Play(IMediaFile file);
        }
        /// <summary>
        /// Default player
        /// </summary>
        public class Player : IPlayer
        {
            public void Play(IMediaFile file)
            {
                Console.WriteLine(file.FilePath);
            }
        }
        /// <summary>
        /// Media files
        /// </summary>
        public interface IMediaFile
        {
            string FilePath { get; set; }
        }
        /// <summary>
        /// Default media file
        /// </summary>
        public class MediaFile : IMediaFile
        {
            public string FilePath { get; set; }
        }

    Give OperationMain class a constructor, because Unity has a constructor injection, and the calling code is as follows:

  4. static UnityContainer container = new UnityContainer();
            static void init()
            {
                container.RegisterType<IPlayer, Player>();
                container.RegisterType<IMediaFile, MediaFile>();
            }
            static void Main(string[] args)
            {
    
                init();
    
                OperationMain op1 = container.Resolve<OperationMain>();
                op1.PlayMedia();
                OperationMain op3 = container.Resolve<OperationMain>();
                op3.PlayMedia();
    
                //Ordinary way
                OperationMain op2 = new OperationMain(new Player(), new MediaFile());
                op2.PlayMedia();
    
                Console.Read();
            }

    You can see that Unity has more functions than these. You can register more than N during initialization and use it directly in the future instead of using new. There are also flexible controls such as instance cycle control and configuration file. For details, see the description of Unity (specifically not the scope of this section).

    5. Summary

    Through a small example, the optimization from simple to deep has deepened the understanding of IoC mode. I think the complex structure is also accumulated from this simple architecture.

Topics: .NET