一、前言

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

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

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

    public class Logger
{
public void AddLogger()
{
Console.WriteLine("日志新增成功!");
}
}

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

        static void Main(string[] args)
{
Logger logger = new Logger();
logger.AddLogger(); Console.Read();
}

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

二、开始

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

2.1 第一种方式

2.1.1 控制台方式

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

    /// <summary>
/// 控制台输出
/// </summary>
public class ConsoleLogger
{
public void AddLogger()
{
Console.WriteLine("控制台输出:日志新增成功!");
}
}

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

    /// <summary>
/// 定义一个输出日志的统一类
/// </summary>
public class LoggerServer
{
private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一个私有变量的对象 个私有变量的数字对象
public void AddLogger()
{
consoleLogger.AddLogger();
}
}

控制台输出结果:

    static void Main(string[] args)
{
LoggerServer loggerServer = new LoggerServer();
loggerServer.AddLogger(); Console.Read();
}

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

2.1.2 文本输出

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

    /// <summary>
/// 文本输出
/// </summary>
public class FileLogger
{
public void AddLogger()
{
Console.WriteLine("文本输出:日志新增成功!");
}
}

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

    /// <summary>
/// 定义一个输出日志的统一类
/// </summary>
public class LoggerServer
{
private readonly FileLogger fileLogger = new FileLogger();//添加一个私有变量的对象
public void AddLogger()
{
fileLogger.AddLogger();
}
}

最后输出结果:

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

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

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

2.2 第二种方式

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

    public interface ILogger
{
void AddLogger();
}

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

    /// <summary>
/// 控制台输出
/// </summary>
public class ConsoleLogger : ILogger
{
public void AddLogger()
{
Console.WriteLine("控制台输出:日志新增成功!");
}
}

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

    /// <summary>
/// 文本输出
/// </summary>
public class FileLogger : ILogger
{
public void AddLogger()
{
Console.WriteLine("文本输出:日志新增成功!");
}
}

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

/// <summary>
/// 定义一个获取验证的统一类
/// </summary>
public class VerificationServer
{
private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一个私有变量的对象
//private readonly FileLogger fileLogger = new FileLogger();//添加一个私有变量的对象
public void AddLogger()
{
_logger.AddLogger();
}
}

最后,控制台调用输出:

    static void Main(string[] args)
{
LoggerServer loggerServer = new LoggerServer();
loggerServer.AddLogger(); Console.Read();
}

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

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

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

2.3 思考

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

三、引入

3.1 依赖注入

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

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

3.1.1 构造函数注入

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

public class LoggerServer
{
private ILogger _logger; //1. 定义私有变量
//2.构造函数
public LoggerServer(ILogger logger)
{
//3.注入 ,传递依赖
this._logger = logger;
} public void AddLogger()
{
_logger.AddLogger();
}
}

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

        static void Main(string[] args)
{
#region 构造函数注入
// 注入控制台输出方式
// 外部创建依赖的对象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
// 通过构造函数注入 -> LoggerServer
LoggerServer loggerServer1 = new LoggerServer(console);
loggerServer1.AddLogger(); // 注入 文件输出方式
FileLogger file = new FileLogger();
// 通过构造函数注入 -> LoggerServer
LoggerServer loggerServer2 = new LoggerServer(file);
loggerServer2.AddLogger(); #endregion Console.Read();
}

输出:

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

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

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

3.1.2 属性注入

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

    /// <summary>
/// 定义一个输出日志的统一类
/// </summary>
public class LoggerServer
{
//1.定义一个属性,可接收外部赋值依赖
public ILogger _logger { get; set; }
public void AddLogger()
{
_logger.AddLogger();
}
}

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

        static void Main(string[] args)
{
#region 属性注入
// 注入 控制台输出方式 //外部创建依赖的对象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
LoggerServer loggerServer1 = new LoggerServer();
//给内部的属性赋值
loggerServer1._logger = console;
loggerServer1.AddLogger(); // 注入 文件输出方式 //外部创建依赖的对象 -> FileLogger
FileLogger file = new FileLogger();
LoggerServer loggerServer2 = new LoggerServer();
//给内部的属性赋值
loggerServer2._logger = file;
loggerServer2.AddLogger(); #endregion Console.Read();
}

输出

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

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

3.1.3 接口注入

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

    public interface IDependent
{
void SetDepend(ILogger logger);//设置依赖项
}

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

    public class VerificationServer : IDependent
{
private ILogger _logger;
// 继承接口,并实现依赖项方法,注入依赖
public void SetDepend(ILogger logger)
{
_logger = logger;
}
public void AddLogger()
{
_logger.AddLogger();
}
}

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

        static void Main(string[] args)
{
#region 接口注入
// 注入 控制台输出方式
//外部创建依赖的对象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
LoggerServer loggerServer1 = new LoggerServer();
//给内部赋值,通过接口的方式传递
loggerServer1.SetDepend(console);
loggerServer1.AddLogger(); //注入 文件输出方式
//外部创建依赖的对象 -> FileLogger
FileLogger file = new FileLogger();
LoggerServer loggerServer2 = new LoggerServer();
//给内部赋值,通过接口的方式传递
loggerServer2.SetDepend(file);
loggerServer2.AddLogger(); #endregion Console.Read();
}

输出

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

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

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之间存在一种耦合关系,并且是紧密耦合在一起。

public class A
{
private B b = new B();//主动的new一个B的对象。主动创建出来
public void Get()
{
B.Create();
}
}

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

public class A
{
private B b;//外部new出来, 注入到引用中
public void Get()
{
B.Create();
}
}

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 v-on-clickaway

    vue v-on-clickaway Custom directive 自定义指令 https://stackoverflow.com/questions/36170425/detect-click- ...

  2. Open Collective

    Open Collective Open Collective is an online funding platform for open and transparent communities. ...

  3. HTML5 QRCode Scaner

    HTML5 QRCode Scaner how to scan QR Code using the camera of the phone or website live demo https://c ...

  4. 调整是为了更好的上涨,牛市下的SPC空投来了!

    2021年刚过没几天,比特币就开启了牛市的旅程,BTC涨到4万美元,ETH涨到1300多美元,BGV也涨到了621.05美元,牛市已然来袭. 虽然从近两日,比特币带领着主流币进行了一波调整,但是只涨不 ...

  5. PAUL ADAMS ARCHITECT:费城东北区的房地产市场逆势而行

    根据Zillow.com的房产数据,大费城地区前三季度成交房产的平均价格为27.2万美元,较去年同期增长了13.4%,为10年同期最高.即使如此,27.2万的均价与纽约相比依然相距甚远,其中尤其是费城 ...

  6. MySQL学习04(DQL查询)

    DQL查询 DQL语言 DQL( Data Query Language 数据查询语言 ) 查询数据库数据 , 如SELECT语句 简单的单表查询或多表的复杂查询和嵌套查询 是数据库语言中最核心,最重 ...

  7. Java 动态调试技术原理及实践

    本文转载自Java 动态调试技术原理及实践 导语 断点调试是我们最常使用的调试手段,它可以获取到方法执行过程中的变量信息,并可以观察到方法的执行路径.但断点调试会在断点位置停顿,使得整个应用停止响应. ...

  8. day1 分布式基础概念

    1. 分布式:一个业务分拆多个子业务,部署在不同的服务器上集群:同一个业务,部署在多个服务器上节点:集群中的一个服务器 2.远程调用 分布式系统中调用其它主机 springcloud用http+jso ...

  9. ElasticSearch 中的 Mapping

    公号:码农充电站pro 主页:https://codeshellme.github.io 1,ES 中的 Mapping ES 中的 Mapping 相当于传统数据库中的表定义,它有以下作用: 定义索 ...

  10. MySQL学习笔记(六)

    好耶,七天课程的最后一天!我当然还没精通了,,,之后可能是多练习题目然后再学学其他的东西吧.mysql新的知识点也会在后面补充的. 一.杂七杂八补充 1. 当多个函数共用同样的参数时,可以转变成类进行 ...