设计模式—控制反转(Ioc)

更新时间:2023-05-06 19:21:26 阅读: 评论:0

设计模式—控制反转(Ioc)
本篇⽬录:
  好长时间没有更新设计模式系列了,不是不想写,奈何⼩菜功⼒有限,这段时间也在给⾃⼰充电,毕竟路要⼀步⼀步⾛,急不得。
  控制反转(Inversion of Control)是解决程序耦合问题的⼀种⽅案,还有种叫法是依赖注⼊(Dependency Injection),但我感觉Ioc(控制反转)是⼀种思想,DI(依赖注⼊)是实现这种思想的⼀种⽅式,或者说Ioc是⼀种概念,DI是这种概念的思想,不知道我这样理解的对不对。可能⼀开始接触这些东西有点莫名其妙,园友们写的⼀些东西也看得头疼,⾄少我当时是这样,如果你是像我⼀样的菜鸟,请跟我⼀起学习下,不看代码,我们先看⼀个⽣活中的例⼦-压⽔井和⾃来⽔⼚的故事。
  内容有点多,请坚持往下看哦!
压⽔井
  ⼩时候在农村喝⽔都是⾃家打井或是⽤电⽔泵取⽔,想什么时候喝就什么时候喝,想喝多少就喝多少,很⽅便,⽽且不⽤花钱。但是有个问题是,家⾥⾯的房⼦要装修或是重建,原来打的井已经不适合新建的房⼦了,也就是说需要重新打井,这就很⿇烦,建多少次房⼦,需要打多少次的井(当然⼟豪才这样)
  我们先看这个⼩⽰例,其实如果抽象⼀点的话,有点类似⼯⼚模式,为什么?我们分析下:上⾯例⼦中的⽔可以看成⼀个产品,每家的井或是电⽔泵可以看成⼀个⼯⼚,⾃⼰根据⾃家的情况来“⽣产”出来⽔,只有⼀家有井或是电⽔泵还好(其他家去他家取⽔,但不现实),如果每家都有⼀个井或是电⽔泵,就有点⼯⼚泛滥的情况发⽣了,可能会出现:
⽔污染:每家都吃不上⽔,这⾥⾯的⽔出现问题就是产品出现问题,这样我们就需要在每个⼯⼚⾥⾯进⾏处理,就⽐如需要在每家的井或电⽔泵上安装⼀个净⽔器,显然代价⽐较⼤,也不太现实。
整体搬迁:原来的井或电⽔泵⽤不了了,每家的井或电⽔泵就需要重新搞,可能不太现实,当然只是做个假设,细想⼀下,这个问题的根源其实就是井或电⽔泵太多了,也就是⼯⼚泛滥。
  上⾯所说的问题为什么会出现?其实就是依赖关系作祟,每⼀家都要依赖⾃家的井或电⽔泵,也没办法,毕竟⼈要喝⽔,总不能跑到地下暗河去喝吧,只能通过井或电⽔泵(⼯⼚)来取⽔(调⽤),这个问题在编程中就是依赖倒置原则的反例,何为依赖倒置原则:
⾼层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
抽象不应该依赖于具体,具体应该依赖于抽象。
  第⼀点:⾼层次模块(使⽤者)就是每户⼈家,低层次模块(被使⽤者)就是压⽔井或电⽔泵,可以看出他们都是依赖于具体的对象,⽽并⾮依赖于抽象;第⼆点:⽔(抽象)依赖压⽔井或电⽔泵(具体),⼈(具体)依赖压⽔井(具体),⽽并⾮具体依赖于抽象。可以看出这两点完全不设和依赖倒置原则,怎么解决问题呢?请看下⾯。
⾃来⽔⼚
  上⾯的⽰例中其实有个三个对象:每户⼈家、压⽔井或电⽔泵、⽔,就是在探讨他们三个这之间的依赖关系,明确这⼀点很重要。
  随着时代的发展,压⽔井和电⽔泵慢慢消失在我们的视野中(当然现在还有很多落后的地⽅在⽤,⽐如像我⽼家),政府就在每个村庄或是⼏个村庄之间建设⾃来⽔⼚,为什么政府要建设⾃来⽔⼚?难道他们都是搞编程的?知道⼯⼚泛滥的坏处?哈哈,我觉得应该是多收点钱吧,你觉得呢?开个玩笑。
  不管政府⽬的如何,但好像解决了⼯⼚泛滥的⼀些问题,我们再来分析下有了⾃来⽔⼚会有什么不同,我们画个⽰意图看下:
  画的⽐较丑(莫笑),但是简单的意思还是可以表达的,图中的⼈和⽔都是抽象的,地下⽔和⽔库
依赖于于抽象的⽔,A村的⼈和B村的⼈依赖于抽象的⼈,⼈和⽔怎么关系呢?这个就有⾃来⽔⼚决定了,它让你喝地下⽔,你就不能喝⽔库的⽔。这就基本符合依赖倒置原则:抽象不依赖于具体,具体依赖于抽象。
  这中间关键在于⾃来⽔⼚,没了压⽔井,有了⾃来⽔⼚,我们看看上⾯压⽔井的“⼯⼚泛滥”问题能不能解决?
⽔污染:⽐如地下⽔出现问题,因为⾃来⽔⼚不依赖地下⽔,⽽是依赖于抽象的⽔,地下⽔有问题,那我⽤⽔库的⽔,⽔库的⽔如果有问题,那我们⽤⾬⽔净化。。。我们⼈喝到的不管什么⽔?反正都是⽔,不影响我们喝⽔就⾏了。
整体搬迁:⽐如A村的⼈因为某些原因,要搬到B村,如果是上⾯压⽔井的模式,帮过去就需要重新打井了,但是有了⾃来⽔⼚,我只需要接个管线,按个⽔龙头就⾏了,就这么简单。
  从上⾯的分析来看,建设⾃来⽔⼚确实⽐压⽔井可靠多了,回到我们这篇要讲的正题-控制反转(Ioc),你可能也有些明⽩了,其实⾃来⽔⼚就可以看做是Ioc,⽤什么样的⽔?给什么样的⼈?都是⾃来⽔⼚决定,好处就不⽤多说了,上⾯已经讲明,套⽤到编程⾥⾯是相同的道理,只可意会哦。
  说到这⾥,你不禁有些惊讶,难道政府⾥⾯有系统架构师?哈哈笑过。
  上⾯的⽰例,下⾯我们再来⽤代码复述⼀下,毕竟理论要结合实践。
压⽔井的问题-依赖
  压⽔井模式有三个对象:⼈、压⽔井、⽔,我们就⽤常规的⽅式简单写下代码:
5        {
6public void DrinkWater()
7            {
8                PressWater pw = new PressWater();
9                UndergroundWater uw = pw.returnWater();
10if (uw!=null)
11                {
12                    Console.WriteLine("地下⽔好甜啊");
13                }
14            }
15        }
16///<summary>
17///压⽔井
18///</summary>
19public class PressWater
20        {
21public UndergroundWater returnWater()
22            {
23return new UndergroundWater();
24            }
25        }
26///<summary>
27///地下⽔
28///</summary>
29public class UndergroundWater
30        {
31        }
  上⾯的代码就是简单演⽰村民通过压⽔井喝⽔的过程,因为村民不能直接取得⽔,只能通过压⽔井取得地下⽔,很明显我们可以看出之间的依赖关系:VillagePeople依赖于PressWater
VillagePeople依赖于UndergroundWater
PressWater依赖于UndergroundWater
  我们在做业务处理的时候,简单的依赖关系可以⽤上⾯的⽅式处理,如果太复杂的话就不⾏了,牵⼀发⽽动全⾝总归不是很好。
  ⼤家可能说,上⾯不是讲到“⼯⼚泛滥”问题,这边怎么没指出?因为PressWater某⼀⽅⾯其实就可以看做⼀个⼩⼯⼚,每家的压⽔井不⼀样,这边只是说某⼀种,“⼯⼚泛滥”其实就是依赖作祟,上⾯的例⼦说明的是依赖关系,⼀样的道理,所以下⾯就⽤这个例⼦来做⼀些东西。
压⽔井的问题解决-依赖倒置
  我们在讲压⽔井的时候提到过依赖倒置原则,这边就不再说了,因为VillagePeople依赖于PressWater、VillagePeople依赖于
UndergroundWater、PressWater依赖于UndergroundWater,我们可以把PressWater(压⽔井)和UndergroundWater(地下⽔)抽象出来,UndergroundWater属于⽔的⼀种,可以抽象为IWater,PressWater因为是获取⽔的⽅式之⼀,可以抽象为IWaterTool,这边就要⾯向接⼝编程了,根据依赖倒置原则,我们把上⾯的代码修改⼀下:
5        {
6public void DrinkWater()
7            {
8                IWaterTool pw = new PressWater();
9                IWater uw = pw.returnWater();
10if (uw != null)
11                {
12                    Console.WriteLine("⽔好甜啊");
13                }
14            }
15        }
16///<summary>
17///压⽔井
18///</summary>
19public class PressWater : IWaterTool
20        {
21public IWater returnWater()
22            {
23return new UndergroundWater();
24            }
25        }
26///<summary>
27///获取⽔⽅式接⼝
28///</summary>
29public interface IWaterTool
30        {
31            IWater returnWater();
32        }
33///<summary>
34///地下⽔
35///</summary>
36public class UndergroundWater : IWater
37        { }
38///<summary>
39///⽔接⼝
40///</summary>
41public interface IWater
42        { }
  从上⾯的代码可以看出,UndergroundWater依赖接⼝IWater,PressWater依赖IWaterTool和IWater,VillagePeople依赖IWaterTool和IWater,这样就符合依赖倒置原则了,都是依赖于抽象,从⽽降低耦合度,这样当⼀个⽅式变化了不会影响到其他,地下⽔污染了,我可以通过别的获取⼯具获取⽔,⽽不⾄于没⽔喝。  但是上⾯说的忽略了个问题,接⼝总是会被实现的,也就是总会执⾏:IWaterTool pw = new PressWater();这样耦合度就产⽣了,也就是VillagePeople依赖于PressWater,我们可以通过⼯⼚参数来产⽣不同的获取⼯具对象,这种⽅式表⾯上虽然解决了问题,但是实质上代码耦合度并没有改变,怎么办呢?请接着往下看。
⾃来⽔⼚-Ioc
  通过Ioc模式可以彻底解决上⾯我们提到耦合的问题,它把耦合从代码中移出去,放到统⼀的XML⽂件中,通过⼀个容器在需要的时候把这个依赖关系形成,即把需要的接⼝实现注⼊到需要它的类中。就像⾃来⽔⼚⼀样,⽔的来源、⽔的去处都是它来决定,⼈们只要通过它来喝⽔就⾏了,⽽不需要考虑的太多。
  早在微软提供的⼀个⽰例框架PetShop中就有Ioc的体现,只不过那时候不太懂,PetShop是通过反射创建对象,上⾯的代码我们修改⼀下:
1///<summary>
2///村民
3///</summary>
4public class VillagePeople
5        {
6public void DrinkWater()
7            {
8                IWaterTool pw = (IWaterTool)Asmbly.Load(ConfigurationManager.AppSettings["AsmName"]).CreateInstance(ConfigurationManager.AppSettings["WaterToolName"]);
9                IWater uw = pw.returnWater();
10if (uw != null)
11                {
12                    Console.WriteLine("⽔好甜啊");
13                }
14            }
15        }
  上⾯代码中我们只需要在配置⽂件中添加获取⽔⼯具的名称WaterToolName就⾏了,因为⼀种⼯具对应获取特定的⼀种⽔,所以⽔的种类不需要配置。地下⽔污染了,我们只需要在配置⽂件中修改⼀下WaterToolName就可以了。
  Ioc模式,系统中通过引⼊实现了Ioc模式的Ioc容器,即可由Ioc容器来管理对象的⽣命周期、依赖关系等,从⽽使得应⽤程序的配置和依赖性规范与实际的应⽤程序代码分开。其中⼀个特点就是通过⽂本的配置⽂件进⾏应⽤程序组件间相互关系的配置,⽽不⽤重新修改并编译具体的代码。
  看到这⾥,是不是感觉Ioc模式有点“热插拔”的意思?有点像USB⼀样呢?
⾃来⽔⼚运⾏-DI
  如果把⾃来⽔⼚看做Ioc,那我觉得依赖注⼊(DI)就是这个⾃来⽔⼚的运⾏模式,当然其实是⼀个意思,依赖注⼊是什么?全称Dependency Injection,我们从字⾯上理解下:需要的接⼝实现注⼊到需要它的类中,这就是依赖注⼊的意思。⾃来⽔⼚获取⽔源的时候,控制这个获取⽔源的开关可以看做是依赖注⼊的⼀种体现,话不多说,懂得就好。
  依赖注⼊的⽅式有很多,就像控制获取⽔源的开关有很多⼀样。
构造器注⼊(Constructor Injection):Ioc容器会智能地选择选择和调⽤适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,Ioc容器在调⽤构造函数之前解析注册的依赖关系并⾃⾏获得相应参数对象;
属性注⼊(Property Injection):如果需要使⽤到被依赖对象的某个属性,在被依赖对象被创建之后,Ioc容器会⾃动初始化该属性;
⽅法注⼊(Method Injection):如果被依赖对象需要调⽤某个⽅法进⾏相应的初始化,在该对象创建之后,Ioc容器会⾃动调⽤该⽅法。
  有时间可以好好研究下依赖注⼊的各种⽅式,这边我们就使⽤微软提供的Unity实现依赖注⼊,⽅式
是构造器注⼊,⾸先使⽤Nuget⼯具将Unity添加到项⽬中,安装Unity需要 framework4.5⽀持。
  添加完之后,发下项⽬中多了Microsoft.Practices.Unity和Microsoft.Practices.Configuation两个dll,代码如下:
1///<summary>
2///⼈接⼝
3///</summary>
4public interface IPeople
5        {
6void DrinkWater();
7        }
8///<summary>
9///村民
10///</summary>
11public class VillagePeople : IPeople
12        {
13            IWaterTool _pw;
14public VillagePeople(IWaterTool pw)
15            {
16                _pw = pw;
17            }
18public void DrinkWater()
19            {
20                IWater uw = _pw.returnWater();
21if (uw != null)
22                {
23                    Console.WriteLine("⽔好甜啊");
24                }
25            }
26        }
  调⽤代码:
1static void Main(string[] args)
2        {
3            UnityContainer container = new UnityContainer();
4            container.RegisterType<TestFour.IWaterTool, TestFour.PressWater>();
5            TestFour.IPeople people = container.Resolve<TestFour.VillagePeople>();
6            people.DrinkWater();
7        }
  ⾸先我们创建⼀个Unity容器,接下来我们需要在容器中注册⼀种类型,它是⼀个类型的映射,接⼝类型是IWaterTool,返回类型为PressWater,这个过程中就是要告诉容易我要注册的类型。
  ⽐如⾃来⽔⼚要⽤地下⽔作为⽔源,这时候操作员输⼊命令,就是RegisterType,参数为IWaterTool、PressWater,下⾯就是调⽤Resolve⽣成对象,这个过程表⽰要把⽔输送到哪户⼈家,命令是Resolve,参数为VillagePeople,接下来就是直接打开⽔龙头喝⽔了,很⽅便吧。
  关于依赖注⼊其实有很多的东西,上⾯的⽰例只是抛砖引⽟,有时间的话好好研究下,⽐如依赖注⼊的其他⽅式等等。

本文发布于:2023-05-06 19:21:26,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/90/98352.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:依赖   需要   问题   倒置   时候   依赖于   代码
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图