• 我曾想深入了解的:依赖倒置、控制反转、依赖注入


    大道至简

    我们在软件工程中进行的架构设计、模块实现、编码等工作,很多时候说到底就是围绕一件事进行:解耦。

    三层架构,MVC,微服务,DDD.我们分析问题,抽象问题,然后划分边界,划分层次。

    也是为了让我们的类、模块、系统有更强的复用能力,提高生产效率。

    这一次,我想深入了解和探讨我曾经很迷糊,也没有一直仔细了解的:依赖倒置、控制反转、依赖注入 这些概念。

    什么是依赖?

    通常可以理解为一种需要,需求。需要协助才能完成一件事情。
    

    例如,我们依赖日志服务写日志:

        public class Contract
        {
           public void Successed()
            {
                string msg = "save, successed!";
                Log log = new Log();
                log.Write(msg);
            }
        }
    

    Contract类正依赖Log类,协助完成整个业务流程。这就产生了依赖。

    什么是抽象? 什么是细节?

    我们经常会听说,面向接口编程,依赖于抽象不能依赖于具体实现细节。

    我们每次修改接口时候,一定会去修改具体实现。但是我们修改具体实现却很少修改接口。

    所以接口比具体实现更稳定。

    此时,我们在中间加入一层接口,看看如何。

        public interface ILog
        {
            void Write();
        }
    
         public void Successed()
         {
             string msg = "save, successed!";
             
             ILog log = new Log();
             log.Write(msg );
         }
    

    关系变化如图:

    什么是上层模块? 什么是底层模块?

    此时,Contract类可以看做上层。Log看做底层。

    上层模块:指挥控制

    底层模块:策略实现

    依赖倒置

    理清楚了 上层、底层、细节、抽象、依赖概念,

    我们不难发现,上面的依赖箭头发生了改变。

    所以依赖倒置也由此而来:

    上层模块不应该依赖底层模块,它们都应该依赖于抽象。
    抽象不应该依赖于细节,细节应该依赖于抽象。
    

    依赖倒置,使得我们的扩展性增强。

    public class Log:ILog
    public class NLog : ILog
    public class Log4 : ILog
    
    // ILog log = new Log();
    // ILog log = new Log4();
    ILog log = new NLog();
    log.Write(msg);
    

    以上代码我们也可以看出,我们需要不断注释修改Contract类,以至于引用不同的Log组件来应对需求。

    每次都要修改这个类来满足需求(修改关闭,扩展开放原则),显然是我们所不希望的。造成这种现象的原因是:

    因为对于上层的Contract类,不仅仅负责业务逻辑的实现,第二职责还要负责日志实例的构造。

    对于Program类,有日志服务类直接拿来使用即可,不需要关心这些实例的构造。

    有没有一种机制能够将构造和使用进行分离?使得Contract的职责更加单一,耦合更低?(单一职责原则)

    控制反转

    怎么算是控制反转了呢?
    

    我们改一下上面的代码将日志类的实例化控制权,转移到类的外部:

        public class Contract
        {
          public Contract(ILog log)
        }
    

    调用

    
        class Program
        {
            static void Main(string[] args)
            {
                ILog log = new NLog();
                Contract contract = new Contract(log);
                contract.Successed();
            }
        }
    

    这样,无论外部日志组件如何变化,都不用会影响现有的Contract类。

    Contract只专注于属于自己的职责。上层Contract类和日志类解耦更加彻底。互不影响。

    如果从职责角度来看,我们是不是可以有一个类专门来管理创建日志类呢?

    就像仓库管理员一样,根据单子出货,不需要关心这些货物到底如何被使用的。

         public static class Ioc
        {
            public static ILog GetLogInstance(int type)
            {
                switch (type)
                {
                    case 1: return new Log();
                    case 2: return new Log4();
                    case 3: return new NLog();
                    default: return new Log();
                }
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {           
                ILog log = Ioc.GetLogInstance(1);
                Contract contract = new Contract(log);
                contract.Successed();
            }
        }
    

    依赖注入

    什么是依赖注入呢?
    

    其实我们刚刚已经实现过了,全文先是依赖倒置,然后控制反转,而现在说的依赖注入是控制反转的具体实现方式。

    依赖注入是解开依赖并实现反转的一种手段。

    大约分为三种方式:

    • 构造函数方式
        public class Contract
        {
            private ILog _log { get; set; }
            public Contract(ILog log)
            {
                _log = log;
            }
        }
    

    优点: 构造Contract就确定好依赖。
    缺点:后期无法更改依赖。

    • Set方式注入
         public class Contract
        {
            private ILog _log { get; set; }
            public Contract(ILog log)
            {
                _log = log;
            }
            public void Successed()
            {
                string msg = "save, successed!";
                _log.Write(msg);
            }
    
            public void SetLogInstance(ILog log)
            {
                _log = log;
            }
        }
    

    优点: 将Log实例化延迟,Contract类可以灵活变动依赖。
    缺点:使用_log前需要判断null情况

    • 接口方式注入
         public interface ILogSetter
        {
            ILog Setter(ILog log);
        }
    
        public class Contract: ILogSetter
        {
            ... ...
            public ILog Setter(ILog log)
            {
                _log = log;
                return _log;
            }
        }
    

    接口方式和方式二有点类似,这里将依赖注入提升为一种能力,可以支配依赖关系的能力。

    探讨 控制反转

    从这个图中,可以看到依赖的倒置,这里低层定义接口并继承实现,高层引用低层定义的接口进行调用使用。

    那么现在的控制权是在低层,那么定义接口这个权力到底属于谁?

    比如我们有一个流程对应着5个步骤,这5个步骤又对应着5个接口:A1,A2,A3,A4,A5

    业务系统a,需要使用这个流程就需要依次调用这5个接口:A1 -> A2 -> A3 -> A4 -> A5

    现在这个流程非常公用,已经上升成为企业级中台的一个流程,越来越多的业务系统给都在对接。

    此时,很多的业务系统需要重新用代码组织一套这样的调用流程,当然现在的控制权还是在业务系统这里。

    所以此时我们对外公开的5个接口,只是简单提供了调用能力,对于接口的编排全部寄希望于业务系统。

    这个时候会有两种声音:
    1、业务系统不想这么繁琐地重复着编排这些接口
    2、中台也想把流程控制权掌握在自己手中,这样遇到业务流程的整体性变更,业务系统是不需要调整的

    业务流程引擎的加入,就像是我们的接口一样,它负责接口的编排,然后成为业务流程。

    此时,控制权实际在接口提供方。

    模式

    我们想使用微软提供的MVC框架,只要是遵循MVC框架的约定就能拥有MVC的能力。

    MVC的控制权在框架,应用想通过框架提供的MVC能力就必须按照框架的定义去做。

    如果框架仅仅是给i我们提供类似于类库一样的MVC实现,

    那么整个流程是应用系统自己根据文档,调用各种类库文件,编排这些实现满足业务系统的MVC需求

    所以,现在的Asp.Net Core 给我们提供的MVC,只要是我们遵循mvc约定,引擎就会推动整个信息的流动,最终反馈给应用。

    这种比较普适的流程或者方案,我们可以成为模式,类似于设计模式,MVC模式.

    原来散落在业务系统中的控制权,反向转到模式中。

    总结

    依赖倒置可以很小也可以很大,

    控制反转也可以很小也可以很大。

    这种思想我们无时无刻可以碰到。

  • 相关阅读:
    fork 入门
    java 注解 @Retention @interface 元数据
    JAVA泛型简析
    http数据流 gzip解压方法分析
    gdb调试提示 Missing separate debuginfos
    Vue2.x响应式原理
    观察者模式
    优秀博客收集
    切换npm源的方式
    前端模块化之ES Module
  • 原文地址:https://www.cnblogs.com/sunchong/p/12242994.html
Copyright © 2020-2023  润新知