zoukankan      html  css  js  c++  java
  • 我曾想深入了解的:依赖倒置、控制反转、依赖注入

    大道至简

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

    三层架构,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模式.

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

    总结

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

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

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

  • 相关阅读:
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 实现业务
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 开发流程
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 报表系统集成说明
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 处理报表
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 数据访问
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 分布式应用
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 实现插件
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 对象设计器使用帮助
    基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET平台开发指南 数据层开发
    Jquery 中的CheckBox、 RadioButton、 DropDownList、CheckBoxList、RadioButtonList的取值赋值
  • 原文地址:https://www.cnblogs.com/sunchong/p/12242994.html
Copyright © 2011-2022 走看看