Inversion of Control (IoC 控制反转)

来源:互联网 发布:java 虚拟现实 编辑:程序博客网 时间:2024/05/16 00:31

- 1.控制反转

首先,控制反转可以视为一种设计模式,也可以视为某些编程框架的共同特征;
控制反转的实际意义并不是他字面上表示的(字面意思似乎是原来A控制B,现在变成B控制A了),真正的含义是:
将某一接口具体实现类的控制权从调用类中移除,转交给第三方.
这引出了下面的问题:
“控制权”指的是什么意思?
“第三方”是什么?既然有第三方,那么前两方是什么?他们之间是什么关系?

  • 1.1 场景设定

为了回答上一页中的问题,我们设定一个场景:
在电影<无间道>中,刘德华饰演刘建明,他有一句台词:“对不起,我是警察.”
实际上是这样:<无间道>是个剧本,剧本的那个位置写着这样一句话:
刘建明说:“对不起,我是警察.”
刘德华饰演刘建明(在第一部中,这个角色由陈冠希扮演)
刘德华在演戏之前记住了剧本,当剧本上相应的场景出现的时候,他就会说出刘建明的这句台词.

  • 1.2 实现
public class  LiuDehua {……}public class WuJianDao {  //剧本   public void TimeToSay(){  //刘建明该说台词了       LiuDehua ldh = new LiuDehua();         ldh.Say(“对不起,我是警察!");     }  } 

这里写图片描述

存在问题:这要求编剧在写剧本的时候就决定刘建明这个演员由刘德华饰演并且写在了剧本中,这与事实是不同的(我们知道事实上刘建明一开始是陈冠希饰演的,后来换成由刘德华饰演),而这种实现不便于更换演员

  • 1.3 引入接口
public interface LiuJianming{……}public class LiuDehua : LiuJianming{……}public class WuJianDao {  //剧本   public void TimeToSay(){  //刘建明该说台词了       LiuJianming ldh = new LiuDehua();         ldh.Say(“对不起,我是警察!");     }  } 

这里写图片描述

  • 1.4 引入装配器

拍电影时,存在一个整体协调全体员工的人—导演
导演需要阅读剧本,为每个剧本中的角色指定演员
编剧向下面向角色(注意,角色只是剧本上的人名),向上面向导演,不需要关心这个角色由谁来演(至少通常是这样).
我们可以认为这样的结构更合理一点:导演作为装配器.

 public interface LiuJianming {        void say(string); }  public class LiuDehua : LiuJianming {        void LiuJianming.say(string TaiCi)        {            Console.WriteLine (TaiCi);        }}public class WuJianDao {        private LiuJianming m_LiuJianming;        public LiuJianming LiuJianming {     get => m_LiuJianming;     set => m_LiuJianming = value; //属性,注意区分        }        void TimeToSay()        {            LiuJianming.say(“对不起,我是警察.”);        }    }public class Director{        private WuJianDao m_Wujiandao;        void KaiJi()        {            //指派演员            m_Wujiandao.LiuJianming=new LiuDehua();         }    }

这里写图片描述

  • 1.5 小总结

什么是“控制”?
在这个例子中,控制就是“挑演员”.或者说:决定某个抽象类指针指向的是类树中的哪一个具体实现.更泛化的讲,是决定调用哪个类或者说哪个实例中的方法.
什么是“反转”?
实际上在这个例子中我们没有看见任何的“反转”,但是我们观察到,选演员的权利从编剧手里撤销,转移到导演手里.
依赖没有消失,只是从一个地方转移到另一个地方,意义是什么?
编程实践中,我们总是遵循这样的一条原则:尽量让每次变更少改代码,能不改最好.在这个例子中,剧本可能会经常改,剧本中出现刘建明的地方可能很多,刘建明的演员也可能换,如果剧本和刘德华不隔离,一旦更换演员,可能需要在剧本中更改很多地方,更可怕的是,万一编剧正在改剧本的过程中演员突然更换,将会出现一大堆需要改的地方,改的地方越多越频繁就越容易引入错误,而且这种文本级别的繁琐工作完全可以通过良好的设计而避免,换句话说,这样改来改去,是不明智且毫无意义的,良好的设计是可扩展的.扩展不是改

  • 2.IoC的动机

实际上所有的设计模式和设计思想都是在保证一件事:
提高系统的可扩展性
尽量解除调用耦合
将高耦合操作从变更概率高的地方转移到变更概率低的地方,如果可能的话,将耦合操作隐藏在某些系统机制中,因为这些机制可能永远都不会变更.
设计一个更符合现实情况的系统
也许这只是出于强迫症,但是我们有理由相信现实中的某些情况是久经考验的且已经以利益最大化为前提进行过长期的优化演变.

  • 3.依赖注入

在上面的例子中,我们看到,A类需要调用B类中的funcB(),而出于扩展性的考虑,我们不能在A类中拥有B类的实例,而是利用多态性,只在A类中放一个B的超类接口的指针,这虽然在语法上是成立的,但是超类指针并不能真正调用某个子类里面的方法,只有当这个指针绑定了某个确定的子类对象时,调用点才能完成相应的功能.
给A类中的接口类型指针绑定某个子类对象的操作,叫做依赖注入
在上文的例子中,我们使用C#语言特性“属性”和λ表达式进行了依赖注入,这种方案,我们称之为“setter注入”.“构造注入”就是通过构造方法进行依赖注入,这两种方法是比较容易理解的.

  • 3.1 依赖注入的其他手段

接口注入
将注入方法抽象成接口方法,虽然有一定道理,但是意义不大,不如直接使用setter注入.
容器注入
通过引入“容器”,关键类的实例化操作对开发者关闭,开发者只需要通过配置文件指定他们希望给某个指针关联上某个类的实例就可以直接使用了.
例如,spring就是这样的一个容器,IoC是spring的重要特征之一.
猜想:容器的这种能力的基础是反射机制.
这里写图片描述

  • 4.依赖获取

依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。
由于依赖注入和依赖获取的应用场景稍有不同,我们再定义一个新的情景:
我们编写了一套软件,该软件可以调整界面的风格.
我们有两套风格,一套是Win风格的,一套是Linux风格的.
我们希望能够通过设置切换这两种显示风格.
这里写图片描述

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Xml;using DependencyLocateDemo;namespace DependencyLocateDemo{    internal interface IWindow    {        String ShowInfo();    }    internal sealed class WindowsWindow : IWindow    {        public String Description { get; private set; }        public WindowsWindow()        {            this.Description = "Windows风格按钮";        }        public String ShowInfo()        {            return this.Description;        }    }    internal sealed class LinuxWindow : IWindow    {        public String Description { get; private set; }        public LinuxWindow()        {            this.Description = " Linux风格按钮";        }        public String ShowInfo()        {            return this.Description;        }    }    internal interface IFactory    {        IWindow MakeWindow();    }    internal sealed class WindowsFactory : IFactory    {        public IWindow MakeWindow()        {            return new WindowsWindow();        }    }    internal sealed class LinuxFactory : IFactory    {        public IWindow MakeWindow()        {            return new LinuxWindow();        }    }    internal static class FactoryContainer    {        public static IFactory factory { get; private set; }        static FactoryContainer()        {            XmlDocument xmlDoc = new XmlDocument();            xmlDoc.Load("config.xml");            e xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0];            if ("Windows" == xmlNode.Value)            {                factory = new WindowsFactory();            }            else if ("Linux" == xmlNode.Value)            {                factory = new LinuxFactory();            }            else            {                throw new Exception("Factory Init Error");            }        }    }    class Program    {        static void Main(string[] args)        {            IFactory factory = FactoryContainer.factory;            IWindow window = factory.MakeWindow();            Console.WriteLine("创建 " + window.ShowInfo());            Console.ReadLine();        }    }}

配置文件:

<?xml version="1.0" encoding="utf-8" ?><config>    <factory>Windows</factory></config>
  • 4.2 更进一步

在这个例子中依赖被进一步转移进了工厂方法中.
静态的FactoryContainer通过配置文件挑选正确的工厂子类进行依赖绑定的时候,势必需要有一个多分支判断.
如果我们在增加一种窗口风格,需要给Iwindow和Ifactory各增加一个子类,同时,需要在FactoryContainer中的多分支判断位置添加一个分支,而这违背了“对扩展开放,对修改关闭”的基本原则.
还能进一步修改,以提高扩展能力吗?

  • 4.3 分析

我们为什么需要那个多分支结构?为什么需要在扩展之后改这里的代码?
我们有一个指针需要绑定对象,但是可供绑定的对象有很多,我们需要根据一个配置文件来选择一个正确的对象.
实例化对象要写类名.
扩展新功能时要添加新的类,但是,在添加之前没有人知道这个类叫什么名字.不知道名字,就没办法创建实例,所以这里需要写死.
既然只有当新的类被扩展出来之后我们才知道类名,那就只能在扩展之后添加一句分支.
我们需要一种“预知未来”的能力??!!

  • 4.4 反射:赋予程序预知未来的能力

使用反射机制,我们能用类名创建类的实例
这里的类名是字符串
我们可以从配置文件中获取这个字符串,然后创建对应类的实例,这样就不需要那个多分支判断了.
反射指程序可以访问、检测和修改它本身状态或行为的一种能力.没有反射的时候,你也许有强大的字符串处理能力,但是没办法写一个程序去修改和处理他自己的源代码,而反射干的就是这样一件事,他提供的机制能够进行运行时类型识别,动态构造对象,动态调用方法等.

  • 4.5 使用反射的实现

这里写图片描述

代码:

using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks;using System.Xml;namespace DependencyLocateByReflex{    internal interface IWindow    {        String ShowInfo();    }    internal sealed class WindowsWindow : IWindow    {        public String Description { get; private set; }        public WindowsWindow()        {            this.Description = "Windows风格按钮";        }        public String ShowInfo()        {            return this.Description;        }    }    internal sealed class LinuxWindow : IWindow    {        public String Description { get; private set; }        public LinuxWindow()        {            this.Description = " Linux风格按钮";        }        public String ShowInfo()        {            return this.Description;        }    }    internal static class ReflectionFactory    {        private static String _windowType;        static ReflectionFactory()        {            XmlDocument xmlDoc = new XmlDocument();            xmlDoc.Load("config.xml");            XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];            _windowType = xmlNode.ChildNodes[0].Value;        }        public static IWindow MakeWindow()        {            return Assembly.Load("DependencyLocateByReflex").CreateInstance("DependencyLocateByReflex." + _windowType) as IWindow;        }    }    class Program    {        static void Main(string[] args)        {            IWindow window = ReflectionFactory.MakeWindow();            Console.WriteLine("创建 " + window.ShowInfo());            Console.ReadLine();        }    }}

配置文件:

<?xml version="1.0" encoding="utf-8" ?><config>    <factory>WindowsWindow</factory></config>
  • 5.IoC容器

前面的1-4基本上说明白了控制反转的含义,动机和实现原理,但是这些东西实际上属于“轮子”,那有没有一种组件/框架/包能够直接给我们这些功能呢?
重量级IoC容器
通过外部配置文件使用,并且托管了系统中的所有的类.系统中所有的服务提供类都由容器提供依赖注入服务.例如Spring和Spring.Net,他们是“久经考验的无产阶级战士”,稳定性无需担心,但是庞大且不灵活.
轻量级IoC容器
一般不依赖外部配置文件,而主要使用传参的Setter或Construtor注入,这种IoC容器叫做轻量级IoC容器.这种框架很灵活,使用方便,但往往不稳定,而且依赖点都是程序中的字符串参数.例如Unity(注意这不是你认为的那个unity,这里指的是微软patterns & practices团队推出的轻量级框架,网址:http://unity.codeplex.com/)

  • 6.主要参考

http://blog.csdn.net/u014563989/article/details/55188673
http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html

原创粉丝点击