谈谈对IOC及DI的理解与思考

发表于 3年以前  | 总阅读数:438 次

一、前言

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

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

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

 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容器进行使用说明」,希望对大家有所帮助。

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

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/qopk2jx6LhZ6-OLe2WzRDQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录