一、前言

在实际的开发过程中,我们经常会遇到这样的情况,在进行调试分析问题的时候,经常需要记录日志信息,这时可以采用输出到控制台。

因此,我们通常会定义一个日志类,来实现输出日志。

定义一个生成验证的逻辑处理方法,

  1. public class Logger
  2. {
  3. public void AddLogger()
  4. {
  5. Console.WriteLine("日志新增成功!");
  6. }
  7. }

然后在控制台中输出结果。

  1. static void Main(string[] args)
  2. {
  3. Logger logger = new Logger();
  4. logger.AddLogger();
  5. Console.Read();
  6. }

看着实现的结果,我们以为完成任务了,当其实这才是刚刚开始。

二、开始

相信大家在开发中,都会遇到这种情况,有时需要控制台输出,但也有可能要你输出到文本,数据库或者远程服务器等等,这些都是有可能。因此最初采用直接输出到控制台已经不能满足条件了,所以我们需要将上述代码进行重写改造,实现不同的输出方式。

2.1 第一种方式

2.1.1 控制台方式

用到控制台方式输出的时候:

  1. /// <summary>
  2. /// 控制台输出
  3. /// </summary>
  4. public class ConsoleLogger
  5. {
  6. public void AddLogger()
  7. {
  8. Console.WriteLine("控制台输出:日志新增成功!");
  9. }
  10. }

定义一个获取输出日志的处理逻辑类,因此我们需要定义ConsoleLogger类并初始化

  1. /// <summary>
  2. /// 定义一个输出日志的统一类
  3. /// </summary>
  4. public class LoggerServer
  5. {
  6. private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一个私有变量的对象 个私有变量的数字对象
  7. public void AddLogger()
  8. {
  9. consoleLogger.AddLogger();
  10. }
  11. }

控制台输出结果:

  1. static void Main(string[] args)
  2. {
  3. LoggerServer loggerServer = new LoggerServer();
  4. loggerServer.AddLogger();
  5. Console.Read();
  6. }

控制台输出:日志新增成功!

2.1.2 文本输出

当用到文本输出日志的时候,我们再次定义一个生成文本日志的方式

  1. /// <summary>
  2. /// 文本输出
  3. /// </summary>
  4. public class FileLogger
  5. {
  6. public void AddLogger()
  7. {
  8. Console.WriteLine("文本输出:日志新增成功!");
  9. }
  10. }

然后再次定义一个获取验证的处理逻辑类,因此我们需要定义ImageVerification类并初始化

  1. /// <summary>
  2. /// 定义一个输出日志的统一类
  3. /// </summary>
  4. public class LoggerServer
  5. {
  6. private readonly FileLogger fileLogger = new FileLogger();//添加一个私有变量的对象
  7. public void AddLogger()
  8. {
  9. fileLogger.AddLogger();
  10. }
  11. }

最后输出结果:

文本输出:日志新增成功!

通过以上的方式,我们实现了不同方式输出不同日志方式,但是仔细观察可以发现,这种方式不是一种良好的软件设计方式。

所以可能有人会改成下面第二种方式,以接口来实现。

2.2 第二种方式

定义一个ILogger接口并声明一个AddLogger方法

  1. public interface ILogger
  2. {
  3. void AddLogger();
  4. }

在控制台输出方式中ConsoleLogger类,实现ILogger接口

  1. /// <summary>
  2. /// 控制台输出
  3. /// </summary>
  4. public class ConsoleLogger : ILogger
  5. {
  6. public void AddLogger()
  7. {
  8. Console.WriteLine("控制台输出:日志新增成功!");
  9. }
  10. }

在文本输出日志方式FileLogger类中,实现ILogger接口

  1. /// <summary>
  2. /// 文本输出
  3. /// </summary>
  4. public class FileLogger : ILogger
  5. {
  6. public void AddLogger()
  7. {
  8. Console.WriteLine("文本输出:日志新增成功!");
  9. }
  10. }

定义一个统一的输出日志类LoggerServer

  1. /// <summary>
  2. /// 定义一个获取验证的统一类
  3. /// </summary>
  4. public class VerificationServer
  5. {
  6. private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一个私有变量的对象
  7. //private readonly FileLogger fileLogger = new FileLogger();//添加一个私有变量的对象
  8. public void AddLogger()
  9. {
  10. _logger.AddLogger();
  11. }
  12. }

最后,控制台调用输出:

  1. static void Main(string[] args)
  2. {
  3. LoggerServer loggerServer = new LoggerServer();
  4. loggerServer.AddLogger();
  5. Console.Read();
  6. }

控制台输出:日志新增成功!

虽然第二种方式中,采用了接口来实现,降低耦合,但还是没有达到我们想要的效果,因此以上的两种方式,都不是很好的软件设计方式。

代码可拓展性比较差,以及组件之间存在高度耦合,违反了开放关闭原则,在设计的时候,也应当考虑对扩展开放,对修改关闭

2.3 思考

既然要遵循开放关闭原则,那上面的写法,选择采用控制台日志输出方式所需要的ConsoleLogger创建和依赖都是在统一的日志类LoggerServer内部进行的,既然要使内部不直接存在绑定依赖,那有没有什么方式从外部传递的方式给LoggerServer类内部引用使用呢?

三、引入

3.1 依赖注入

依赖注入 : 它提供一种机制,将需要依赖对象的引用传递给被依赖对象。

下面我们先看看具体的几种注入方式,再做小结说明。

3.1.1 构造函数注入

LoggerServer类中,定义一个私有变量_logger, 然后通过构造函数的方式传递依赖

  1. public class LoggerServer
  2. {
  3. private ILogger _logger; //1. 定义私有变量
  4. //2.构造函数
  5. public LoggerServer(ILogger logger)
  6. {
  7. //3.注入 ,传递依赖
  8. this._logger = logger;
  9. }
  10. public void AddLogger()
  11. {
  12. _logger.AddLogger();
  13. }
  14. }

通过控制台程序调用,先在外部创建依赖对象,而后通过构造的方式注入依赖

  1. static void Main(string[] args)
  2. {
  3. #region 构造函数注入
  4. // 注入控制台输出方式
  5. // 外部创建依赖的对象 -> ConsoleLogger
  6. ConsoleLogger console = new ConsoleLogger();
  7. // 通过构造函数注入 -> LoggerServer
  8. LoggerServer loggerServer1 = new LoggerServer(console);
  9. loggerServer1.AddLogger();
  10. // 注入 文件输出方式
  11. FileLogger file = new FileLogger();
  12. // 通过构造函数注入 -> LoggerServer
  13. LoggerServer loggerServer2 = new LoggerServer(file);
  14. loggerServer2.AddLogger();
  15. #endregion
  16. Console.Read();
  17. }

输出:

控制台输出:日志新增成功!

文本输出:日志新增成功!

显然的发现,通过这种构造函数注入的方式,在外部定义依赖,降低内部的耦合度,同时也增加了扩展性,只需从外部修改依赖,就可以实现不同的验证结果。

3.1.2 属性注入

即通过定义一个属性来传递依赖

  1. /// <summary>
  2. /// 定义一个输出日志的统一类
  3. /// </summary>
  4. public class LoggerServer
  5. {
  6. //1.定义一个属性,可接收外部赋值依赖
  7. public ILogger _logger { get; set; }
  8. public void AddLogger()
  9. {
  10. _logger.AddLogger();
  11. }
  12. }

通过控制台,定义不同的方式,通过不同依赖赋值,实现不同的验证结果:

  1. static void Main(string[] args)
  2. {
  3. #region 属性注入
  4. // 注入 控制台输出方式
  5. //外部创建依赖的对象 -> ConsoleLogger
  6. ConsoleLogger console = new ConsoleLogger();
  7. LoggerServer loggerServer1 = new LoggerServer();
  8. //给内部的属性赋值
  9. loggerServer1._logger = console;
  10. loggerServer1.AddLogger();
  11. // 注入 文件输出方式
  12. //外部创建依赖的对象 -> FileLogger
  13. FileLogger file = new FileLogger();
  14. LoggerServer loggerServer2 = new LoggerServer();
  15. //给内部的属性赋值
  16. loggerServer2._logger = file;
  17. loggerServer2.AddLogger();
  18. #endregion
  19. Console.Read();
  20. }

输出

控制台输出:日志新增成功!

文本输出:日志新增成功!

3.1.3 接口注入

先定义一个接口,包含一个设置依赖的方法。

  1. public interface IDependent
  2. {
  3. void SetDepend(ILogger logger);//设置依赖项
  4. }

这个与之前的注入方式不一样,而是通过在类中继承并实现这个接口

  1. public class VerificationServer : IDependent
  2. {
  3. private ILogger _logger;
  4. // 继承接口,并实现依赖项方法,注入依赖
  5. public void SetDepend(ILogger logger)
  6. {
  7. _logger = logger;
  8. }
  9. public void AddLogger()
  10. {
  11. _logger.AddLogger();
  12. }
  13. }

通过调用,直接通过依赖项方法,传递依赖

  1. static void Main(string[] args)
  2. {
  3. #region 接口注入
  4. // 注入 控制台输出方式
  5. //外部创建依赖的对象 -> ConsoleLogger
  6. ConsoleLogger console = new ConsoleLogger();
  7. LoggerServer loggerServer1 = new LoggerServer();
  8. //给内部赋值,通过接口的方式传递
  9. loggerServer1.SetDepend(console);
  10. loggerServer1.AddLogger();
  11. //注入 文件输出方式
  12. //外部创建依赖的对象 -> FileLogger
  13. FileLogger file = new FileLogger();
  14. LoggerServer loggerServer2 = new LoggerServer();
  15. //给内部赋值,通过接口的方式传递
  16. loggerServer2.SetDepend(file);
  17. loggerServer2.AddLogger();
  18. #endregion
  19. Console.Read();
  20. }

输出

控制台输出:日志新增成功!

文本输出:日志新增成功!

3.1.4 小结

依赖注入(DI—Dependency Injection)

它提供一种机制,将需要依赖对象的引用传递给被依赖对象通过DI,我们可以在LoggerServer类在外部ConsoleLogger对象的引用传递给LoggerServer类对象。 注入某个对象所需要的外部资源(包括对象、资源、常量数据)

依赖注入把对象的创造交给外部去管理,很好的解决了代码紧耦合的问题,是一种让代码实现松耦合的机制。

松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试。

3.2 IOC

控制反转(Inversion of Control,缩写为IoC),在面向对象编程中,是一种软件设计模式,教我们如何设计出更优良,更具有松耦合的程序。

在上文的例子中,我们发现如果在获取对象的过程中靠类内部主动创建依赖对象,则会导致代码直接高度耦合并且期存在难以维护这种隐患,所以为了避免这种问题,我们采用了由外部提供依赖对象,内部对象类被创建的时候,将其所依赖的对象引用传递给它,实现了依赖被注入到对象中去。

通俗的说明:

在类A中用到了类B的对象时候,一般情况下,需要在A的代码中显式的new一个B的对象。这种方式都是通过我们自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个对象,就主动去创建,创建对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起。

  1. public class A
  2. {
  3. private B b = new B();//主动的new一个B的对象。主动创建出来
  4. public void Get()
  5. {
  6. B.Create();
  7. }
  8. }

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。现在创建对象而是有第三方控制创建,你要什么对象,它就给你什么对象,依赖关系就变了,原先的依赖关系就没了,A和B之间耦合度也就减少了。

  1. public class A
  2. {
  3. private B b;//外部new出来, 注入到引用中
  4. public void Get()
  5. {
  6. B.Create();
  7. }
  8. }

3.3 关系

控制反转(IoC) 是一种软件设计的模式,指导我们设计出更优良,更具有松耦合的程序,

而具体的实现方式依赖注入依赖查找

在这一篇主要说的是常用的依赖注入方式。

你在实际开发中,可能还会听到另一名词叫 IoC容器,这其实是一个依赖注入的框架

用来映射依赖,管理对象创建和生存周期。 (在后续篇章会具体说明)

四、思考

说到依赖,就想到依赖注入和工厂模式这两者的区别?

这是网上有一个对比例子:

工厂设计模式 依赖注入
对象创建 它用于创建对象。我们有单独的Factory类,其中包含创建逻辑。 它负责创建和注入对象。
对象的状态 它负责创建有状态对象。 负责创建无状态对象
运行时/编译时间 在编译时创建对象 在运行时配置对象
代码变更 如果业务需求发生变化,则可能会更改对象创建逻辑。 无需更改代码
机制 类依赖于工厂方法,而工厂方法又依赖于具体类 父对象和所有从属对象可以在单个位置创建

好啦,这篇文章就先讲述到这里吧,在后续篇章中会对常用的IOC容器进行使用说明,希望对大家有所帮助。

如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。

谈谈对IOC及DI的理解与思考的更多相关文章

  1. IoC和DI的理解

    1 概述 当我们想闭上眼睛想如何让我们的软件更加可用可维护时,我们总能想到一个词:松耦合.在这篇文章中,主要讲述了模块间存在的依赖关系,但这种依赖关系违背了依赖倒置原则.在这之后,我们将讨论一种解除软 ...

  2. 浅谈ASP.NET Core中IOC与DI的理解和使用

    说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...

  3. Spring 学习教程(一):浅谈对Spring IOC以及DI的理解

    一.个人对IoC(控制反转)和DI(依赖注入)的理解我们平时在开发java web程序的时候,每个对象在需要使用它的合作对象时,自己都要将它要合作对象创建出来(比如 new 对象),这个合作对象是由自 ...

  4. 对Spring中IOC和DI的理解

    前几篇讲了Spring中IOC和DI的用法,本篇应该放到三篇之前,但一直没有想到好的讲解方式,后参考https://blog.csdn.net/luoyepiaoxue2014/article/det ...

  5. 关于IOC和DI的理解

    IOC:Inversion of Control 控制反转 DI:Dependency Injection 依赖注入 控制反转,从字面意思来看,就是控制权又被动变主动,最后又变回被动. 举个例子: 你 ...

  6. IoC与DI的理解

    首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/b ...

  7. 重温IOC,DI的理解

    IOC和DI其实它们是同一个概念的不同角度描述 IOC强调的是程序控制对象(创建销毁),变换成了容器来控制对象(创建销毁) DI:即IoC容器帮对象找相应的依赖对象通过反射注入     从Spring ...

  8. 深入谈谈 Java IOC 和 DI

    1.前言 不得不说, IOC和DI 在写代码时经常用到.还有个就是在面试时 ,面试官老喜欢问 IOC 和DI是什么的问题,都快被问吐了, 可是,仍然会让许多人说的支支吾吾. 为什么? 第一,因为这个知 ...

  9. 框架面试题:谈谈我对Spring IOC与DI的理解

    IOC是一种叫做“控制反转”的设计思想. 1.较浅的层次——从名字上解析 “控制”就是指对 对象的创建.维护.销毁等生命周期的控制,这个过程一般是由我们的程序去主动控制的,如使用new关键字去创建一个 ...

随机推荐

  1. vue 如何重绘父组件,当子组件的宽度变化时候

    vue 如何重绘父组件,当子组件的宽度变化时候 vue & dynamic el-popover position demo https://codepen.io/xgqfrms/pen/wv ...

  2. css delete line text & html del

    css delete line text & html del html <del>¥720</del> demo <span class="ticke ...

  3. js & class & init

    js & class & init how to call class init method in js when create an instance 在初始化类实例的时候调用,类 ...

  4. NGK公链:去中心化交易+挖矿生态体系共舞

    NGK生态公链是一个安全.透明.专业的去中心化商业应用平台.作为一条具有技术信任甚至是公众信任的公链,NGK以区块链技术为支撑,利用区块链透明.公正.公开.数据不可篡改.分布式存储.可追溯等技术优势, ...

  5. VS Code使用Git可视化管理源代码详细教程

    前言: 随着VS Code的功能和插件的不断强大和完善,它已经成为了我们日常开发中一个必不可缺的伙伴了.在之前我曾经写过一篇SourceTree使用教程详解(一个git可视化管理神器,想要了解的话可以 ...

  6. Android 之 EditText

    1.使用EditText 的SetInput的方法设置输入类型: 1 //输入类型为没有指定明确的类型的特殊内容类型 2 editText.setInputType(InputType.TYPE_NU ...

  7. Python_20行代码实现微信消息防撤回(简易版)

    学习了一下如何用python实现微信消息的防撤回, 主要思路就是: 时时监控微信,将对方发送的消息缓存下来 如果对方撤回了消息,就将该缓存信息发送给文件传输助手 但其实这功能,基本上毫无意义,看到别人 ...

  8. 死磕Spring之IoC篇 - 调试环境的搭建

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  9. 正则表达式匹配${key}并在Java中使用

    1.正则表达式匹配${key} \$\{([a-z]+)\} 能够匹配字符串中以${key}形式的文本(其中key为小写应为字母) .*\$\{([a-z]+)\}.* 可以用来检测文本中是否有${k ...

  10. 解决浏览器点击button出现边框问题

    发现问题 本人不懂浏览器的HTML代码 不知道怎么在chrome浏览器的F12之后点到了哪里 点击button的时候就会出现黑色边框 解决 终于发现不是因为动了调试页面,而是动了谷歌浏览器的高级选项, ...