Spiga

依赖注入那些事儿

2009-06-17 00:26 by T2噬菌体, 20370 visits, 收藏, 编辑

目录

目录

1 IGame游戏公司的故事

    1.1 讨论会

    1.2 实习生小李的实现方法

    1.3 架构师的建议

    1.4 小李的小结

2 探究依赖注入

    2.1 故事的启迪

    2.2 正式定义依赖注入

3 依赖注入那些事儿

    3.1 依赖注入的类别

        3.1.1 Setter注入

        3.1.2 Construtor注入

        3.1.3 依赖获取

    3.2 反射与依赖注入

    3.3 多态的活性与依赖注入

        3.3.1 多态性的活性

        3.3.2 不同活性多态性依赖注入的选择

4 IoC Container

    4.1 IoC Container出现的必然性

    4.2 IoC Container的分类

        4.2.1 重量级IoC Container

        4.2.2 轻量级IoC Container

    4.3 .NET平台上典型IoC Container推介

        4.3.1 Spring.NET

        4.3.2 Unity

参考文献

1 IGame游戏公司的故事

1.1 讨论会

话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此获得经验、虚拟货币和虚拟装备),并且根据玩家角色所装备的武器不同,攻击效果也不同。这天,IGame公司的开发小组正在开会对打怪功能中的某一个功能点如何实现进行讨论,他们面前的大屏幕上是这样一份需求描述的ppt:

图1.1 需求描述ppt

各个开发人员,面对这份需求,展开了热烈的讨论,下面我们看看讨论会上都发生了什么。

1.2 实习生小李的实现方式

在经过一番讨论后,项目组长Peter觉得有必要整理一下各方的意见,他首先询问小李的看法。小李是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是IGame公司的一名实习生。

经过短暂的思考,小李阐述了自己的意见:

“我认为,这个需求可以这么实现。HP当然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可以使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,然后据此对被攻击怪物的HP进行操作,以产生不同效果。”

而在阐述完后,小李也飞快的在自己的电脑上写了一个Demo,来演示他的想法,Demo代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    /// <summary>
    /// 怪物
    /// </summary>
    internal sealed class Monster
    {
        /// <summary>
        /// 怪物的名字
        /// </summary>
        public String Name { get; set; }
 
        /// <summary>
        /// 怪物的生命值
        /// </summary>
        public Int32 HP { get; set; }
 
        public Monster(String name,Int32 hp)
        {
            this.Name = name;
            this.HP = hp;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    /// <summary>
    /// 角色
    /// </summary>
    internal sealed class Role
    {
        private Random _random = new Random();
 
        /// <summary>
        /// 表示角色目前所持武器的字符串
        /// </summary>
        public String WeaponTag { get; set; }
 
        /// <summary>
        /// 攻击怪物
        /// </summary>
        /// <param name="monster">被攻击的怪物</param>
        public void Attack(Monster monster)
        {
            if (monster.HP <= 0)
            {
                Console.WriteLine("此怪物已死");
                return;
            }
 
            if ("WoodSword" == this.WeaponTag)
            {
                monster.HP -= 20;
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "损失20HP");
                }
            }
            else if ("IronSword" == this.WeaponTag)
            {
                monster.HP -= 50;
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "损失50HP");
                }
            }
            else if ("MagicSword" == this.WeaponTag)
            {
                Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
                monster.HP -= loss;
                if (200 == loss)
                {
                    Console.WriteLine("出现暴击!!!");
                }
 
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻击成功!怪物" + monster.Name + "损失" + loss + "HP");
                }
            }
            else
            {
                Console.WriteLine("角色手里没有武器,无法攻击!");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    class Program
    {
        static void Main(string[] args)
        {
            //生成怪物
            Monster monster1 = new Monster("小怪A", 50);
            Monster monster2 = new Monster("小怪B", 50);
            Monster monster3 = new Monster("关主", 200);
            Monster monster4 = new Monster("最终Boss", 1000);
 
            //生成角色
            Role role = new Role();
 
            //木剑攻击
            role.WeaponTag = "WoodSword";
            role.Attack(monster1);
 
            //铁剑攻击
            role.WeaponTag = "IronSword";
            role.Attack(monster2);
            role.Attack(monster3);
 
            //魔剑攻击
            role.WeaponTag = "MagicSword";
            role.Attack(monster3);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
 
            Console.ReadLine();
        }
    }
}

程序运行结果如下:

图1.2 小李程序的运行结果

1.3 架构师的建议

小李阐述完自己的想法并演示了Demo后,项目组长Peter首先肯定了小李的思考能力、编程能力以及初步的面向对象分析与设计的思想,并承认小李的程序正确完成了需求中的功能。但同时,Peter也指出小李的设计存在一些问题,他请小于讲一下自己的看法。

小于是一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深入的认识。他向Peter点了点头,发表了自己的看法:

“小李的思考能力是不错的,有着基本的面向对象分析设计能力,并且程序正确完成了所需要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。

首先,小李设计的Role类的Attack方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。

再者,我认为这个设计比较大的一个问题是,违反了OCP原则。在这个设计中,如果以后我们增加一个新的武器,如倚天剑,每次攻击损失500HP,那么,我们就要打开Role,修改Attack方法。而我们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。

一般来说,当一个方法里面出现冗长的if…else或switch…case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。而这里,如果把不同武器攻击看成一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。

最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。”

Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。

Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间可以相互替换,此模式使得算法的变化独立于客户。

小于边说,边画了一幅UML类图,用于直观表示他的思想。

图1.3 小于的设计

Peter让小李按照小于的设计重构Demo,小李看了看小于的设计图,很快完成。相关代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    internal interface IAttackStrategy
    {
        void AttackTarget(Monster monster);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    internal sealed class WoodSword : IAttackStrategy
    {
        public void AttackTarget(Monster monster)
        {
            monster.Notify(20);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    internal sealed class IronSword : IAttackStrategy
    {
        public void AttackTarget(Monster monster)
        {
            monster.Notify(50);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    internal sealed class MagicSword : IAttackStrategy
    {
        private Random _random = new Random();
 
        public void AttackTarget(Monster monster)
        {
            Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
            if (200 == loss)
            {
                Console.WriteLine("出现暴击!!!");
            }
            monster.Notify(loss);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    /// <summary>
    /// 怪物
    /// </summary>
    internal sealed class Monster
    {
        /// <summary>
        /// 怪物的名字
        /// </summary>
        public String Name { get; set; }
 
        /// <summary>
        /// 怪物的生命值
        /// </summary>
        private Int32 HP { get; set; }
 
        public Monster(String name,Int32 hp)
        {
            this.Name = name;
            this.HP = hp;
        }
 
        /// <summary>
        /// 怪物被攻击时,被调用的方法,用来处理被攻击后的状态更改
        /// </summary>
        /// <param name="loss">此次攻击损失的HP</param>
        public void Notify(Int32 loss)
        {
            if (this.HP <= 0)
            {
                Console.WriteLine("此怪物已死");
                return;
            }
 
            this.HP -= loss;
            if (this.HP <= 0)
            {
                Console.WriteLine("怪物" + this.Name + "被打死");
            }
            else
            {
                Console.WriteLine("怪物" + this.Name + "损失" + loss + "HP");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    /// <summary>
    /// 角色
    /// </summary>
    internal sealed class Role
    {
        /// <summary>
        /// 表示角色目前所持武器
        /// </summary>
        public IAttackStrategy Weapon { get; set; }
 
        /// <summary>
        /// 攻击怪物
        /// </summary>
        /// <param name="monster">被攻击的怪物</param>
        public void Attack(Monster monster)
        {
            this.Weapon.AttackTarget(monster);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    class Program
    {
        static void Main(string[] args)
        {
            //生成怪物
            Monster monster1 = new Monster("小怪A", 50);
            Monster monster2 = new Monster("小怪B", 50);
            Monster monster3 = new Monster("关主", 200);
            Monster monster4 = new Monster("最终Boss", 1000);
 
            //生成角色
            Role role = new Role();
 
            //木剑攻击
            role.Weapon = new WoodSword();
            role.Attack(monster1);
 
            //铁剑攻击
            role.Weapon = new IronSword();
            role.Attack(monster2);
            role.Attack(monster3);
 
            //魔剑攻击
            role.Weapon = new MagicSword();
            role.Attack(monster3);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
 
            Console.ReadLine();
        }
    }
}

编译运行以上代码,得到的运行结果与上一版本代码基本一致。

1.4 小李的小结

Peter显然对改进后的代码比较满意,他让小李对照两份设计和代码,进行一个小结。小李简略思考了一下,并结合小于对一次设计指出的不足,说道:

“我认为,改进后的代码有如下优点:

第一,虽然类的数量增加了,但是每个类中方法的代码都非常短,没有了以前Attack方法那种很长的方法,也没有了冗长的if…else,代码结构变得很清晰。

第二,类的职责更明确了。在第一个设计中,Role不但负责攻击,还负责给怪物减少HP和判断怪物是否已死。这明显不应该是Role的职责,改进后的代码将这两个职责移入Monster内,使得职责明确,提高了类的内聚性。

第三,引入Strategy模式后,不但消除了重复性代码,更重要的是,使得设计符合了OCP。如果以后要加一个新武器,只要新建一个类,实现IAttackStrategy接口,当角色需要装备这个新武器时,客户代码只要实例化一个新武器类,并赋给Role的Weapon成员就可以了,已有的Role和Monster代码都不用改动。这样就实现了对扩展开发,对修改关闭。”

Peter和小于听后都很满意,认为小李总结的非常出色。

IGame公司的讨论会还在进行着,内容是非常精彩,不过我们先听到这里,因为,接下来,我们要对其中某些问题进行一点探讨。别忘了,本文的主题可是依赖注入,这个主角还没登场呢!让主角等太久可不好。

2 探究依赖注入

2.1 故事的启迪

我们现在静下心来,再回味一下刚才的故事。因为,这个故事里面隐藏着依赖注入的出现原因。我说过不只一次,想真正认清一个事物,不能只看“它是什么?什么样子?”,而应该先弄清楚“它是怎么来的?是什么样的需求和背景促使了它的诞生?它被创造出来是做什么用的?”。

回想上面的故事。刚开始,主要需求是一个打怪的功能。小李做了一个初步面向对象的设计:抽取领域场景中的实体(怪物、角色等),封装成类,并为各个类赋予属性与方法,最后通过类的交互完成打怪功能,这应该算是面向对象设计的初级阶段。

在小李的设计基础上,架构师小于指出了几点不足,如不符合OCP,职责划分不明确等等,并根据情况引入策略模式。这是更高层次的面向对象设计。其实就核心来说,小于只做了一件事:利用多态性,隔离变化。它清楚认识到,这个打怪功能中,有些业务逻辑是不变的,如角色攻击怪物,怪物减少HP,减到0怪物就会死;而变化的仅仅是不同的角色持有不同武器时,每次攻击的效用不一样。于是他的架构,本质就是把变化的部分和不变的部分隔离开,使得变化部分发生变化时,不变部分不受影响。

我们再仔细看看小于的设计图,这样设计后,有个基本的问题需要解决:现在Role不依赖具体武器,而仅仅依赖一个IAttackStrategy接口,接口是不能实例化的,虽然Role的Weapon成员类型定义为IAttackStrategy,但最终还是会被赋予一个实现了IAttackStrategy接口的具体武器,并且随着程序进展,一个角色会装备不同的武器,从而产生不同的效用。赋予武器的职责,在Demo中是放在了测试代码里。

这里,测试代码实例化一个具体的武器,并赋给Role的Weapon成员的过程,就是依赖注入!这里要清楚,依赖注入其实是一个过程的称谓!

2.2 正式定义依赖注入

下面,用稍微正式一点的语言,定义依赖注入产生的背景缘由和依赖注入的含义。在读的过程中,读者可以结合上面的例子进行理解。

依赖注入产生的背景:

随着面向对象分析与设计的发展,一个良好的设计,核心原则之一就是将变化隔离,使得变化部分发生变化时,不变部分不受影响(这也是OCP的目的)。为了做到这一点,要利用面向对象中的多态性,使用多态性后,客户类不再直接依赖服务类,而是依赖于一个抽象的接口,这样,客户类就不能在内部直接实例化具体的服务类。但是,客户类在运作中又客观需要具体的服务类提供服务,因为接口是不能实例化去提供服务的。就产生了“客户类不准实例化具体服务类”和“客户类需要具体服务类”这样一对矛盾。为了解决这个矛盾,开发人员提出了一种模式:客户类(如上例中的Role)定义一个注入点(Public成员Weapon),用于服务类(实现IAttackStrategy的具体类,如WoodSword、IronSword和MagicSword,也包括以后加进来的所有实现IAttackStrategy的新类)的注入,而客户类的客户类(Program,即测试代码)负责根据情况,实例化服务类,注入到客户类中,从而解决了这个矛盾。

依赖注入的正式定义:

依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。

3 依赖注入那些事儿

上面我们从需求背景的角度,讲述了依赖注入的来源和定义。但是,如果依赖注入仅仅就只有这么点东西,那也没有什么值得讨论的了。但是,上面讨论的仅仅是依赖注入的内涵,其外延还是非常广泛的,从依赖注入衍生出了很多相关的概念与技术,下面我们讨论一下依赖注入的“那些事儿”。

3.1 依赖注入的类别

依赖注入有很多种方法,上面看到的例子中,只是其中的一种,下面分别讨论不同的依赖注入类型。

3.1.1 Setter注入

第一种依赖注入的方式,就是Setter注入,上面的例子中,将武器注入Role就是Setter注入。正式点说:

Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。

图3.1 Setter注入示意

上图展示了Setter注入的结构示意图,客户类ClientClass设置IServiceClass类型成员_serviceImpl,并设置Set_ServiceImpl方法作为注入点。Context会负责实例化一个具体的ServiceClass,然后注入到ClientClass里。

下面给出Setter注入的示例代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal interface IServiceClass
    {
        String ServiceInfo();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal class ServiceClassA : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassA";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal class ServiceClassB : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassB";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal class ClientClass
    {
        private IServiceClass _serviceImpl;
 
        public void Set_ServiceImpl(IServiceClass serviceImpl)
        {
            this._serviceImpl = serviceImpl;
        }
 
        public void ShowInfo()
        {
            Console.WriteLine(_serviceImpl.ServiceInfo());
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    class Program
    {
        static void Main(string[] args)
        {
            IServiceClass serviceA = new ServiceClassA();
            IServiceClass serviceB = new ServiceClassB();
            ClientClass client = new ClientClass();
 
            client.Set_ServiceImpl(serviceA);
            client.ShowInfo();
            client.Set_ServiceImpl(serviceB);
            client.ShowInfo();
        }
    }
}

运行结果如下:

图3.2 Setter注入运行结果

3.1.2 构造注入

另外一种依赖注入方式,是通过客户类的构造函数,向客户类注入服务类实例。

构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。

图3.3 构造注入示意

图3.3是构造注入的示意图,可以看出,与Setter注入很类似,只是注入点由Setter方法变成了构造方法。这里要注意,由于构造注入只能在实例化客户类时注入一次,所以一点注入,程序运行期间是没法改变一个客户类对象内的服务类实例的。

由于构造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是一样的,所以这里给出另外ClientClass类的示例代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConstructorInjection
{
    internal class ClientClass
    {
        private IServiceClass _serviceImpl;
 
        public ClientClass(IServiceClass serviceImpl)
        {
            this._serviceImpl = serviceImpl;
        }
 
        public void ShowInfo()
        {
            Console.WriteLine(_serviceImpl.ServiceInfo());
        }
    }
}

可以看到,唯一的变化就是构造函数取代了Set_ServiceImpl方法,成为了注入点。

3.1.3 依赖获取

上面提到的注入方式,都是客户类被动接受所依赖的服务类,这也符合“注入”这个词。不过还有一种方法,可以和依赖注入达到相同的目的,就是依赖获取。

依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。

可以看到,这种方法变被动为主动,使得客户类在需要时主动获取服务类,而将多态性的实现封装到获取点里面。获取点可以有很多种实现,也许最容易想到的就是建立一个Simple Factory作为获取点,客户类传入一个指定字符串,以获取相应服务类实例。如果所依赖的服务类是一系列类,那么依赖获取一般利用Abstract Factory模式构建获取点,然后,将服务类多态性转移到工厂的多态性上,而工厂的类型依赖一个外部配置,如XML文件。

不过,不论使用Simple Factory还是Abstract Factory,都避免不了判断服务类类型或工厂类型,这样系统中总要有一个地方存在不符合OCP的if…else或switch…case结构,这种缺陷是Simple Factory和Abstract Factory以及依赖获取本身无法消除的,而在某些支持反射的语言中(如C#),通过将反射机制的引入彻底解决了这个问题(后面讨论)。

下面给一个具体的例子,现在我们假设有个程序,既可以使用Windows风格外观,又可以使用Mac风格外观,而内部业务是一样的。

图3.4 依赖获取示意

上图乍看有点复杂,不过如果读者熟悉Abstract Factory模式,应该能很容易看懂,这就是Abstract Factory在实际中的一个应用。这里的Factory Container作为获取点,是一个静态类,它的“Type构造函数”依据外部的XML配置文件,决定实例化哪个工厂。下面还是来看示例代码。由于不同组件的代码是相似的,这里只给出Button组件的示例代码,完整代码请参考文末附上的完整源程序。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal interface IButton
    {
        String ShowInfo();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal sealed class WindowsButton : IButton
    {
        public String Description { get; private set; }
 
        public WindowsButton()
        {
            this.Description = "Windows风格按钮";
        }
 
        public String ShowInfo()
        {
            return this.Description;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal sealed class MacButton : IButton
    {
        public String Description { get; private set; }
 
        public MacButton()
        {
            this.Description = " Mac风格按钮";
        }
 
        public String ShowInfo()
        {
            return this.Description;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal interface IFactory
    {
        IWindow MakeWindow();
 
        IButton MakeButton();
 
        ITextBox MakeTextBox();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal sealed class WindowsFactory : IFactory
    {
        public IWindow MakeWindow()
        {
            return new WindowsWindow();
        }
 
        public IButton MakeButton()
        {
            return new WindowsButton();
        }
 
        public ITextBox MakeTextBox()
        {
            return new WindowsTextBox();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal sealed class MacFactory : IFactory
    {
        public IWindow MakeWindow()
        {
            return new MacWindow();
        }
 
        public IButton MakeButton()
        {
            return new MacButton();
        }
 
        public ITextBox MakeTextBox()
        {
            return new MacTextBox();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
 
namespace DependencyLocate
{
    internal static class FactoryContainer
    {
        public static IFactory factory { get; private set; }
 
        static FactoryContainer()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load("http://www.cnblogs.com/Config.xml");
            XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0];
 
            if ("Windows" == xmlNode.Value)
            {
                factory = new WindowsFactory();
            }
            else if ("Mac" == xmlNode.Value)
            {
                factory = new MacFactory();
            }
            else
            {
                throw new Exception("Factory Init Error");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    class Program
    {
        static void Main(string[] args)
        {
            IFactory factory = FactoryContainer.factory;
            IWindow window = factory.MakeWindow();
            Console.WriteLine("创建 " + window.ShowInfo());
            IButton button = factory.MakeButton();
            Console.WriteLine("创建 " + button.ShowInfo());
            ITextBox textBox = factory.MakeTextBox();
            Console.WriteLine("创建 " + textBox.ShowInfo());
 
            Console.ReadLine();
        }
    }
}

这里我们用XML作为配置文件。配置文件Config.xml如下:

<?xml version="1.0" encoding="utf-8" ?>
<config>
    <factory>Mac</factory>
</config>

可以看到,这里我们将配置设置为Mac风格,编译运行上述代码,运行结果如下:

图3.5 配置Mac风格后的运行结果

现在,我们不动程序,仅仅将配置文件中的“Mac”改为Windows,运行后结果如下:

图3.6 配置为Windows风格后的运行结果

从运行结果看出,我们仅仅通过修改配置文件,就改变了整个程序的行为(我们甚至没有重新编译程序),这就是多态性的威力,也是依赖注入效果。

本节共讨论了三种基本的依赖注入类别,有关更多依赖注入类别和不同类别对比的知识,可以参考Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》。

3.2 反射与依赖注入

回想上面Dependency Locate的例子,我们虽然使用了多态性和Abstract Factory,但对OCP贯彻的不够彻底。在理解这点前,朋友们一定要注意潜在扩展在哪里,潜在会出现扩展的地方是“新的组件系列”而不是“组件种类”,也就是说,这里我们假设组件就三种,不会增加新的组件,但可能出现新的外观系列,如需要加一套Ubuntu风格的组件,我们可以新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,并分别实现相应接口,这是符合OCP的,因为这是扩展。但我们除了修改配置文件,还要无可避免的修改FactoryContainer,需要加一个分支条件,这个地方破坏了OCP。依赖注入本身是没有能力解决这个问题的,但如果语言支持反射机制(Reflection),则这个问题就迎刃而解。

我们想想,现在的难点是出在这里:对象最终还是要通过“new”来实例化,而“new”只能实例化当前已有的类,如果未来有新类添加进来,必须修改代码。如果,我们能有一种方法,不是通过“new”,而是通过类的名字来实例化对象,那么我们只要将类的名字作为配置项,就可以实现在不修改代码的情况下,加载未来才出现的类。所以,反射给了语言“预见未来”的能力,使得多态性和依赖注入的威力大增。

下面是引入反射机制后,对上面例子的改进:

图3.7 引入反射机制的Dependency Locate

可以看出,引入反射机制后,结构简单了很多,一个反射工厂代替了以前的一堆工厂,Factory Container也不需要了。而且以后有新组件系列加入时,反射工厂是不用改变的,只需改变配置文件就可以完成。下面给出反射工厂和配置文件的代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;
 
namespace DependencyLocate
{
    internal static class ReflectionFactory
    {
        private static String _windowType;
        private static String _buttonType;
        private static String _textBoxType;
 
        static ReflectionFactory()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load("http://www.cnblogs.com/Config.xml");
            XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];
 
            _windowType = xmlNode.ChildNodes[0].Value;
            _buttonType = xmlNode.ChildNodes[1].Value;
            _textBoxType = xmlNode.ChildNodes[2].Value;
        }
 
        public static IWindow MakeWindow()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow;
        }
 
        public static IButton MakeButton()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton;
        }
 
        public static ITextBox MakeTextBox()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox;
        }
    }
}

配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<config>
    <window>MacWindow</window>
    <button>MacButton</button>
    <textBox>MacTextBox</textBox>
</config>

反射不仅可以与Dependency Locate结合,也可以与Setter Injection与Construtor Injection结合。反射机制的引入,降低了依赖注入结构的复杂度,使得依赖注入彻底符合OCP,并为通用依赖注入框架(如Spring.NET中的IoC部分、Unity等)的设计提供了可能性。

3.3 多态的活性与依赖注入

3.3.1 多态性的活性

这一节我们讨论多态的活性及其与依赖注入类型选择间密切的关系。

首先说明,“多态的活性”这个术语是我个人定义的,因为我没有找到既有的概念名词可以表达我的意思,所以就自己造了一个词。这里,某多态的活性是指被此多态隔离的变化所发生变化的频繁程度,频繁程度越高,则活性越强,反之亦然。

上文说过,多态性可以隔离变化,但是,不同的变化,发生的频率是不一样的,这就使得多态的活性有所差别,这种差别影响了依赖注入的类型选择。

举例来说,本文最开始提到的武器多态性,其活性非常高,因为在那个程序中,Role在一次运行中可能更换多次武器。而现在我们假设Role也实现了多态性,这是很可能的,因为在游戏中,不同类型的角色(如暗夜精 灵、牛头人、矮人等)很多属性和业务是想通的,所以很可能通过一个IRole或AbstractRole抽象类实现多态性,不过,Role在实例化后(一般在用户登录成功后),是不会变化的,很少有游戏允许同一个玩家在运行中变换Role类型,所以Role应该是一但实例化,就不会变化,但如果再实例化一个(如另一个玩家登录),则可能就变化了。最后,还有一种多态性是活性非常低的,如我们熟悉的数据访问层多态性,即使我们实现了SQL Server、Oracle和Access等多种数据库的访问层,并实现了依赖注入,但几乎遇不到程序运行着就改数据库或短期内数据库频繁变动的情况。

以上不同的多态性,不但特征不同,其目的一般也不同,总结如下:

高活多态性——指在客户类实例运行期间,服务类可能会改变的多态性。

中活多态性——指在客户类实例化后,服务类不会改变,但同一时间内存在的不同实例可能拥有不同类型的服务类。

低活多态性——指在客户类实例化后,服务类不会改变,且同一时间内所有客户类都拥有相同类型的服务类。

以上三种多态性,比较好的例子就是上文提到的武器多态性(高活)、角色多态性(中活)和数据访问层多态性(低活)。另外,我们说一种多态性是空间稳定的,如果同一客户类在同一时间内的所有实例都依赖相同类型的服务类,反之则叫做空间不稳定多态性。我们说一种多态性是时间稳定的,如果一个客户类在实例化后,所以来的服务类不能再次更改,反之则叫做时间不稳定多态性。显然,高活多态性时间和空间均不稳定;中活多态性是时间稳定的,但空间不稳定;低活多态性时间空间均稳定。

3.3.2 不同活性多态的依赖注入选择

一般来说,高活多态性适合使用Setter注入。因为Setter注入最灵活,也是唯一允许在同一客户类实例运行期间更改服务类的注入方式。并且这种注入一般由上下文环境通过Setter的参数指定服务类类型,方便灵活,适合频繁变化的高活多态性。

对于中活多态性,则适合使用Constructor注入。因为Constructor注入也是由上下文环境通过Construtor的参数指定服务类类型,但一点客户类实例化后,就不能进行再次注入,保证了其时间稳定性。

而对于低活多态性,则适合使用Dependency Locate并配合文件配置进行依赖注入,或Setter、Constructor配合配置文件注入,因为依赖源来自文件,如果要更改服务类,则需要更改配置文件,一则确保了低活多态性的时间和空间稳定性,二是更改配置文件的方式方便于大规模服务类替换。(因为低活多态性一旦改变行为,往往规模很大,如替换整个数据访问层,如果使用Setter和Construtor传参,程序中需要改变的地方不计其数)

本质上,这种选择是因为不同的依赖注入类型有着不同的稳定性,大家可以细细体会“活性”、“稳定性”和“依赖注入类型”之间密切的关系。

4 IoC Container

4.1 IoC Container出现的必然性

上面讨论了诸多依赖注入的话题。说道依赖注入,就不能不说IoC Container(IoC容器),那么到底什么是IoC容器?我们还是先来看看它的出现背景。

我们知道,软件开发领域有句著名的论断:不要重复发明轮子!因为软件开发讲求复用,所以,对于应用频繁的需求,总是有人设计各种通用框架和类库以减轻人们的开发负担。例如,数据持久化是非常频繁的需求,于是各种ORM框架应运而生;再如,对MVC的需求催生了Struts等一批用来实现MVC的框架。

随着面向对象分析与设计的发展和成熟,OOA&D被越来越广泛应用于各种项目中,然而,我们知道,用OO就不可能不用多态性,用多态性就不可能不用依赖注入,所以,依赖注入变成了非常频繁的需求,而如果全部手工完成,不但负担太重,而且还容易出错。再加上反射机制的发明,于是,自然有人开始设计开发各种用于依赖注入的专用框架。这些专门用于实现依赖注入功能的组件或框架,就是IoC Container。

从这点看,IoC Container的出现有其历史必然性。目前,最著名的IoC也许就是Java平台上的Spring框架的IoC组件,而.NET平台上也有Spring.NET和Unity等。

4.2 IoC Container的分类

前面曾经讨论了三种依赖注入方式,但是,想通过方式对IoC Container进行分类很困难,因为现在IoC Container都设计很完善,几乎支持所有依赖注入方式。不过,根据不同框架的特性和惯用法,还是可以讲IoC Container分为两个大类。

4.2.1 重量级IoC Container

所谓重量级IoC Container,是指一般用外部配置文件(一般是XML)作为依赖源,并托管整个系统各个类的实例化的IoC Container。这种IoC Container,一般是承接了整个系统几乎所有多态性的依赖注入工作,并承接了所有服务类的实例化工作,而且这些实例化依赖于一个外部配置文件,这种IoC Container,很像通过一个文件,定义整个系统多态结构,视野宏大,想要很好驾驭这种IoC Container,需要一定的架构设计能力和丰富的实践经验。

Spring和Spring.NET是重量级IoC Container的例子。一般来说,这种IoC Container稳定性有余而活性不足,适合进行低活多态性的依赖注入。

4.2.2 轻量级IoC Container

还有一种IoC Container,一般不依赖外部配置文件,而主要使用传参的Setter或Construtor注入,这种IoC Container叫做轻量级IoC Container。这种框架很灵活,使用方便,但往往不稳定,而且依赖点都是程序中的字符串参数,所以,不适合需要大规模替换和相对稳定的低活多态性,而对于高活多态性,有很好的效果。

Unity是一个典型的轻量级IoC Container。

4.3 .NET平台上典型IoC Container推介

4.3.1 Spring.NET

Spring.NET是Java平台上Spring对.NET平台的移植,使用方法和Spring很像,并且功能强大,是.NET平台上大中型开发IoC Container的首选之一。除了DI外,Spring.NET也包括AOP等诸多功能。

Spring.NET的官方网站是:http://www.springframework.net/

4.3.2 Unity

对于小型项目和讲求敏捷的团队,Spring.NET可能有点太重量级,那么可以选择轻量级的Unity。Unity是微软patterns & practices团队推出的轻量级框架,非常好用,目前最新版本是1.2。

Unity的官方网站是:http://unity.codeplex.com/

参考文献

[1]  Shivprasad koirala, Design pattern – Inversion of control and Dependency injection, http://www.codeproject.com/KB/aspnet/IOCDI.aspx

[2]  Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern, http://www.martinfowler.com/articles/injection.html

[3]  Paul, IoC Types, http://docs.codehaus.org/display/PICO/IoC+Types

[4]  Eric Freeman, Elisabeth Freeman. Head First Design Patterns. O’Reilly Media, 2004. ISBN 0596007142

[5]  Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. ISBN 0201633612

[6]  Patrick Smacchia 著,施凡等 译,C#和.NET2.0 平台、语言与框架。2008.1,人民邮电出版

[7]  Jeffrey Rechter 著,CLR via C#(影印版)。2008.8,人民邮电出版

Creative Commons License

本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名张洋(包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

Add your comment

147 条回复

    评论共2页: 上一页 1 2 
  1. #48楼[楼主] EricZhang(T2噬菌体)      2009-06-17 15:41
    @戏水3
    已修改,谢谢
     回复 引用 查看   
  2. #49楼 小强假的[未注册用户]2009-06-17 15:45
    --引用--------------------------------------------------
    只喜欢评论,不喜欢写: 文章的前半部分写得不错,后半部分就不怎么样了。
    要说明DI的优势,建议把工厂模式和ObjectBuilder之间的差异做例子。
    高入侵性和低入侵性,这两个术语真的有点用词不当,一般而言,高低入侵性是指对程序原有结构的影响。Spring.NET通过配置文件实现注入,其实是低入侵性的,Unity的初版只能通过Attribute定义,其实才是高入侵性。
    PS:Unity1.2版已经开始支持外部文件定义注入了。
    --------------------------------------------------------
    我的印象中 高侵入和低侵入也是这样区分的。
     回复 引用   
  3. #50楼 戏水      2009-06-17 15:52
    anytao 和 TerryLee 怎么还不来此贴呢? 菜都凉了 ……
     回复 引用 查看   
  4. #51楼[楼主] EricZhang(T2噬菌体)      2009-06-17 15:56
    多谢楼上各位的提醒,已经将“侵入度”去掉,改为“量级”。另外,楼上指出的错别字已修改。
     回复 引用 查看   
  5. #52楼 MLY@[未注册用户]2009-06-17 16:04
    这字体怎么看不清楚
     回复 引用   
  6. #53楼 蓝色京广线      2009-06-17 16:46

    楼主有心了,收藏!
     回复 引用 查看   
  7. #54楼 reinstallsys      2009-06-17 17:20
    好文章,收藏一下。
     回复 引用 查看   
  8. #55楼 侯垒      2009-06-17 17:42
    好文章。
     回复 引用 查看   
  9. #56楼 Aierduo[未注册用户]2009-06-17 17:52
    多态
    策略模式
    抽象工厂
     回复 引用   
  10. #57楼 老蒋      2009-06-17 18:07
    不错,支持一下,楼主写这么长的文章,花了不少时间吧

    ----------------------------------------
    我的网站 http://www.yeahbeta.com
     回复 引用 查看   
  11. #58楼 Ryanism      2009-06-17 18:31
    写的太好了,值得反复品味,收藏了~
     回复 引用 查看   
  12. #59楼 文超      2009-06-17 19:32
    看完,忍不住叫好。
     回复 引用 查看   
  13. #60楼 CoderZh      2009-06-17 19:39
    写的确实不错。
     回复 引用 查看   
  14. #61楼 Artech      2009-06-17 22:47
    --引用--------------------------------------------------
    只喜欢评论,不喜欢写: 文章的前半部分写得不错,后半部分就不怎么样了。
    要说明DI的优势,建议把工厂模式和ObjectBuilder之间的差异做例子。
    高入侵性和低入侵性,这两个术语真的有点用词不当,一般而言,高低入侵性是指对程序原有结构的影响。Spring.NET通过配置文件实现注入,其实是低入侵性的,Unity的初版只能通过Attribute定义,其实才是高入侵性。
    PS:Unity1.2版已经开始支持外部文件定义注入了。
    --------------------------------------------------------
    “工厂模式和ObjectBuilder之间的差异做例子”,这个Martin已经说得够清楚了!
     回复 引用 查看   
  15. #62楼 dudu      2009-06-17 23:24
    关于依赖注入的经典之作!
    谢谢EricZhang(T2噬菌体) !
    园子里太需要这样的文章了!
     回复 引用 查看   
  16. #63楼[楼主] EricZhang(T2噬菌体)      2009-06-17 23:52
    @dudu
    没想到站长亲自来捧场,实在太荣幸了。。。
     回复 引用 查看   
  17. #64楼 ilclr      2009-06-18 04:16
    提下多态的活性这个概念,个人认为表达不够准确

    楼主所说的活性表示多态隔离的变化的出现频率,也就是所谓的活性其实就是多态这个转变过程对变化频率的一种封装

    一个变化点变化的频率越高,就越应该应用多态去封装这种变化,所以lz的多态的活性更确切的说是多态的意义----多态封装的变化频率越高,多态的封装意义也就越大

    如果硬要找一个概念去描述这个理念,个人推荐----多态的价值度!
     回复 引用 查看   
  18. #65楼 zhouyou96      2009-06-18 09:09
    拜读
     回复 引用 查看   
  19. #66楼[楼主] EricZhang(T2噬菌体)      2009-06-18 12:49
    @ilclr
    多态的活性和封装价值没有必然联系。并不是“多态封装的变化频率越高,多态的封装意义也就越大”。高活的封装和低活的封装目的不同。

    高活的封装是为了应对程序运行中对多态的需要,如例子中武器的变化。

    而低活的多态性往往是为了应对大规模的变化,也很有价值。例如,创业初期做一个SNS社区,刚开始资金不够,网站用户也少,就选择MySQL,但考虑到以后如果运营成功,MySQL可能就不够用了,需要换Oracle或SQLServer,这个变化的频率很低的,也许是一年后,也许是两年后,那么,请问,您是愿意通过多态更新整个数据访问层,还是愿意将整个程序推翻重写呢?

    这种对低活的封装,价值不是依然很大吗?
     回复 引用 查看   
  20. #67楼 ilclr      2009-06-18 14:45
    to LZ

    呵呵,你我的意思是相同的,只不过侧重关注点不同而已。

    任何一种变化按照你的文章意思都有时间成本。可能变化的发生和封装的发生间隔时间很长,例如你提到的SNS DB访问层的多态封装,间隔了1年的时间,也有可能变化的发生和封装的发生间隔时间很短,例如role的sword类型变化。所以,我们在评判一个变化的频率的时候,如果要找一个定量的尺度,那么这个尺度就是变化发生的平均间隔时间(而不是变化的种类)。如果不考虑平均间隔时间,当然,lz所谓的高活和地活的多态意义等同。既然活有高低之分,我个人感觉还是要有一个高低的衡量尺度,这个尺度就是多态封装变化发生的平均间隔时间。

    同样1年的间隔周期,多态A封装的变化changeA发生了280次,而多态B封装的变化changeB发生了8次,就我个人理解,多态A在这1年的周期中体现的价值更大。随意建议用多态的价值度去作为衡量多态意义的概念。
     回复 引用 查看   
  21. #68楼 施杨      2009-06-18 21:24
    好文,深刻。个人觉得已经把依赖注入讲的很透彻了。
     回复 引用 查看   
  22. #69楼 奇里斯玛2009-06-18 23:50
    咋感觉你用的fanfou 就是Twritter 国产山寨版啊 嘿嘿
     回复 引用   
  23. #70楼 ITAres      2009-06-19 11:56
    顶了在看...看完成顶
     回复 引用 查看   
  24. #71楼 刘荣华![未注册用户]2009-06-19 13:29
    眼前一亮。
     回复 引用   
  25. #72楼 woodylic      2009-06-19 13:36
    第一次能耐下心来把依赖注入看完。
    感谢楼主好文!
     回复 引用 查看   
  26. #73楼 Mien++[未注册用户]2009-06-20 16:24
    学习中,关注Unity~ 楼主真有耐心:)
     回复 引用   
  27. #74楼 楼主你好[未注册用户]2009-06-23 16:04
    我想问楼主几个问题。一直在困惑我。


    Role和IAttackStrategy之间是什么关系?

    如果是依赖,那为什么IAttackStrategy作为Role的属性拥有?
    如果是关联,UML图是否该用实线箭头?

    在这里,Role是类,IAttackStrategy是接口,可否用UML中的关联和依赖来分析?

    或者说,即有关联又是依赖,那这个场景的设计是否有更好的方法?

    期待楼主解答。谢谢!!!
     回复 引用   
  28. #75楼 张蒙蒙      2009-06-23 16:36
    楼主你好,我和上楼是朋友,我们都仔细看过你此文,我们交流了下,都有问题想跟你探讨,下面是我的问题,可能有点长,耽误你时间拉。
    接口是行为定义的集合,我有个想法,分解接口,UML说的依赖一般是作为参数使用,
    突然我想到:是否可以用接口做参数替代类持有引用。
    public void Attack(Monster monster,IAttackStrategy weapon)

    23: {

    24: weapon.AttackTarget(monster);

    25: }

    那么我们不用持有这个引用。这个种设计很像楼主说的set注入,这个是方法调用的时刻注入,其活性应该和楼主你说的set注入一样。

    但是我能感觉到这种设计是不好的,在此我思考着为什么接口被人民提炼出来,类持有引用就是关联了,那么如何实现用类持有引用,而不是参数的方式,确不出现关联?这个是不是接口出现的原因呢,持有接口更加像持有一个服务,意图是使用,那么这更加像依赖了。
    可以看一个,依赖和关联同时存在的例子:
    class A{
    private B b;
    public string Say(){
    return b.GetName();
    }
    }
    我们持有了B类b的引用,这个是关联,但是我们使用b的方法GetName().这是依赖B,那么这让人困惑的UML图如何画出来,到这很多人会说,这不过是学术派的思考,真的么,再来看这个例子,其实我们是想使用b不是么?既然是使用我们为什么不用依赖呢,用依赖可以这么办:
    class A{
    public string Say(B b){
    return b.GetName();
    }
    }
    上面是UML定义依赖的一般方法,突然有人说我可能会有很多方法使用b!
    class A{
    public string Say(B b){
    return b.GetName();
    }
    public string Say2(B b){
    return b.GetName()+1;
    }
    public string Say3(B b){
    return b.GetName()+2;
    }
    }
    如果不用持有引用,都这么用参数是不是很失败?对很失败,那么我们现在就出现了想持有引用确不想关联这个对象的时刻!这个时刻是推出接口的时刻。
    有句话叫针对接口编程。到这楼主我絮絮叨叨很多没别的意思,是想跟你交流,说说自己的想法,可能和楼主你文章无关联,不过还是你好文让我想了这么多。

     回复 引用 查看   
  29. #76楼[楼主] EricZhang(T2噬菌体)      2009-06-23 18:00
    @Old
    呵呵。是这样的,写文章要有重点,我这篇文章的重点在于讨论依赖注入,所以例子中的业务能简化就尽量简化,让读者能集中精力理解重点。实际设计中,何止角色和武器,各种元素都有很复杂的集成扩展结构,如果把这些都加上,读者还没看到依赖注入就晕在这个例子上了。

    毕竟,我这篇文章的目的,是和朋友们讨论依赖注入,不是讨论ARPG游戏的业务问题,要分清主次关系。
     回复 引用 查看   
  30. #77楼[楼主] EricZhang(T2噬菌体)      2009-06-23 18:03
    @楼主你好
    这里是依赖。类依赖接口没有问题。IAttackStrategy作为Role的属性拥有,是为了依赖注入使用,从本质上看,还是Role依赖IAttackStrategy。

    我不太明白你的“关联”是什么意思,是指组合和聚合吗?

     回复 引用 查看   
  31. #78楼[楼主] EricZhang(T2噬菌体)      2009-06-23 18:08
    @张蒙蒙
    看了你的分析,真的挺不错的,很多地方我都没有想到。可能是我比较懒的缘故吧,我一般不用“关联”表示,除非是明显的组合或聚合,而习惯于对于客户类和服务类的关系一律使用“依赖”。当然,我这样做其实不够严谨,呵呵。

    我觉得你的分析很有思想,你可以将你的想法整理一下,并向深度和广度做一个扩展,写一篇文章在园子里,我觉得应该会很不错!
     回复 引用 查看   
  32. #79楼[楼主] EricZhang(T2噬菌体)      2009-06-23 18:11
    @楼主你好
    @张蒙蒙

    我又仔细想了一下你们的分析。我个人认为,是否持有引用不是判断是否是关联的唯一标准,是关联还是依赖应该看动机。

    一般,依赖是调用被依赖类的方法,但不持有引用作为成员。但在某些特殊情况下,如依赖注入中,持有引用作为成员,是为了实现依赖注入,而不是将其作为成员,所以就其动机来说,是“依赖”,而不是“关联”。
     回复 引用 查看   
  33. #80楼 八一精神(未登录)[未注册用户]2009-06-23 21:40
    这里接口的目的是为了实现依赖注入。

    关联是两个类之间的关系。

    在类和接口之间的这种情况,还真不好定义是关联还是依赖。


    这可能就是接口出现的原因吧。

    张蒙蒙同学说的有道理。

     回复 引用   
  34. #81楼 张蒙蒙      2009-06-23 22:12
    @八一精神(未登录)
    兄弟你还用好几个名字呢呵呵

    t2说到动机,动机还真能解释,我查看书《uml用户指南2》,关联的主要定义是结构上的包含,依赖是行为上的使用

    接口是行为的集合,是个空虚的功能描述,接口和类不同的是本身无字段躯体。那么对于接口说关联是不合适的,可把接口看作行为,使用 接口可看作使用行为,这样说来,还是依赖比较合适,另外<uml用户指南2>确实对于接口无关联的例子,都是依赖的例子。
    可以确定是依赖,
    t2说动机,依赖注入的动机是依赖次接口,不是关联此接口,我能感觉到t2有种心里明白,口中确不好说,呵呵,可不可理解一个依赖是使用接口,关联是包含类的实例,这里依赖注入的动机是,使用接口,不是想持有接口这个引用。
     回复 引用 查看   
  35. #82楼 张蒙蒙      2009-06-23 22:27
    所以说关联比依赖更大,解耦尽量使用依赖,不过最大的还是继承,那么我想了下,优先使用组合不是继承是初级解耦也最常用,多用依赖少用关联是中级解耦,针对接口编程的依赖替代针对底层子类的引用是高级的解耦.

    真希望t2能多多指点,提出建议,刚才我还真有想借你的人气,引出我山寨的结论——一篇山寨oo乱想哈哈。
     回复 引用 查看   
  36. #83楼 八一精神(未登录)[未注册用户]2009-06-23 22:34
    发现一个问题:
    每次进入这个帖子我的MSN就自动启动……
     回复 引用   
  37. #84楼 Asidy      2009-06-24 09:34
    LZ写的越来越好了,继续关注!
     回复 引用 查看   
  38. #85楼 lhking      2009-06-28 12:56
    每次看完楼主的文章,都不敢妄下断言,只能回去细细揣摩,受益匪浅,顶一个。
     回复 引用 查看   
  39. #86楼 Rhythmk      2009-07-01 17:15
    强烈支持LZ去搞个MVP玩玩...

    凭LZ的乐于分享是绝对有资格拿的!
     回复 引用 查看   
  40. #87楼 CoolCode      2009-07-08 17:50
    写得很详细,谢谢!88楼,对于广东人来说好数字
     回复 引用 查看   
  41. #88楼 铁打的西西。      2009-07-19 19:06
    louzhuqiang
     回复 引用 查看   
  42. #89楼 george.hu      2009-07-20 14:25
    好文章,看得出楼主深厚的功底
     回复 引用 查看   
  43. #90楼 chen_767      2009-07-23 14:53
    理解得好
     回复 引用 查看   
  44. #91楼 陈根      2009-07-26 21:58
    好文章,通俗易懂
     回复 引用 查看   
  45. #92楼 baolong[未注册用户]2009-07-31 16:00
    “利用多态性,隔离变化”这种表达式错误的。多态是结果而不是方法。应该是用抽象或接口隔离变化。这种表达反映出一种隔离变化的方法。
     回复 引用   
  46. #93楼 baolong[未注册用户]2009-07-31 16:03
    多态根本就不是面向对象的精髓。
     回复 引用   
  47. #94楼 baolong[未注册用户]2009-07-31 16:09
    在这个设计中,可把role看作是sword和target的关联类,这样才能体现面向对象的“delegation"。这样看来,DI 是抽象和”委托“的自然结果,而非”故意设计“的结果。我们把头脑中的”合理“想法表达在程序中,自然就可惜合理;从而摆脱为模式而模式。
     回复 引用   
  48. #95楼 baolong[未注册用户]2009-07-31 16:16
    依赖注入好像很高深,其实一个对象聚合就能解决依赖注想解决的问题。注入点就是聚合点。怎么会有客户对象实例化服务对象的想法?这显然不是面向对象的思维。只要有面向对象的思维,DI就没有存在的必要。
     回复 引用   
  49. #96楼 baolong      2009-07-31 16:23
    依赖注入和控制反转的本质是把依赖关系放在配置文件中来表达。
     回复 引用 查看   
  50. #97楼 鸽子飞扬      2009-08-05 14:21
    非常好,支持
     回复 引用 查看   
  51. #98楼 灰灰狼      2009-08-08 10:40
    十分十分十分的强
     回复 引用 查看   
  52. #99楼 一线风      2009-09-02 10:26
    确实比较强悍,理论相关扎实呀.
     回复 引用 查看   
  53. #100楼 的确小凉      2009-09-13 12:45
    强烈支持一下
     回复 引用 查看   
  54. #101楼 monkey9178      2009-11-24 11:02
    好文章,赞一个!
     回复 引用 查看   
  55. #102楼 jordan51341      2009-11-26 12:37
    写得很好 清晰明了
     回复 引用 查看   
  56. #103楼 小学生甲      2009-11-26 16:03
    汗,有问题了,琢磨不透,随便搜索一下,结果N多都是楼主的文章...不佩服不行啊,啥也不说了,眼泪哗哗的
     回复 引用 查看   
  57. #104楼 airwolf2026      2009-12-25 18:47
    楼主的文章我收藏了半年.今天上班偷懒看完了...写的太好啦

    楼主可否分享下你看过的一些其他书籍呀?
     回复 引用 查看   
  58. #105楼 冰河魔法师      2010-01-01 21:37
    很清晰明了,感谢楼主的文章!
     回复 引用 查看   
  59. #106楼 小学生甲      2010-01-04 16:02
    知其然,知其所以然,确实很不错的文章,以前对这个依赖注入总感觉隔了一层迷雾,今天终于明白到底是啥东西了,感谢楼主
     回复 引用 查看   
  60. #107楼 ToBin      2010-01-04 21:28
    这才是真正的好文,找了好久,终于找到正主了
     回复 引用 查看   
  61. #108楼 kevin.li      2010-01-13 09:35
    支持楼主
     回复 引用 查看   
  62. #109楼 Victoria520      2010-01-13 21:10
    不错,值得学习
     回复 引用 查看   
  63. #110楼 Everest      2010-02-01 10:05
    多态的活性与依赖注入关系讲的很好, 这块给了很好的启示, 尤其对初学者的迷雾, 拨云见日.
     回复 引用 查看   
  64. #111楼 fishkuro      2010-04-15 21:56
    不同类型的角色(如暗夜精灵、牛头人、矮人等)很多属性和业务是想通的
    这句有错别字,请检查
     回复 引用 查看   
  65. #112楼 双击      2010-05-07 17:33
    原来
    引用韦恩卑鄙:不错不错 我们这些山寨都不知道自己在做的事就是依赖注入


    同意观点

    真是一篇好文啊
    说的太透彻了
    可惜今天才看见
     回复 引用 查看   
  66. #113楼 str.chan      2010-05-20 11:00
    引用圣水静月:楼主不厚道,这篇文章中80%多都是抄来的,书的名字就是<大话设计模式>,想不到还有这么多人捧场,悲剧啊!


    首先声明,只是针对你一个人,但是博客园里就是多了像你们这样不尊重人的劳动,看东西五六分就认为理解了十分的人才会变的硝烟弥漫,程大神的书我也有看,就算T2的例子是跟程大的例子一样,但是程大讲的主要还是设计模式,但T2的这个文章,是个人都能明白是在讲依赖注入~~~~~有这闲心去说道楼主,不如您老人家也写一篇出来~~
     回复 引用 查看   
  67. #114楼 virus      2010-06-03 18:42
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace BeautyCode.ConsoleApp
    {
    public class Role
    {
    public Role (string name,Weapon weapon)
    {
    this.Name = name;
    this.Weapon = weapon;
    }
    public string Name { get; set; }
    public Weapon Weapon { get; set; }
    public void AttackMonster(Monster monster)
    {
    Console.WriteLine("角色{0}准备攻击怪物{1}", Name, monster.Name);
    this.Weapon.Attack(monster);
    Console.WriteLine("角色{0}攻击怪物{1}结束", Name, monster.Name);
    }
    }
    public class Weapon
    {
    public Weapon(string name, int hurtHp)
    {
    this.Name = name;
    this.HurtHP = hurtHp;
    RandomRate = new Random(DateTime.Now.Millisecond);
    }
    public void Attack(Monster monster)
    {
    Rate= RandomRate.NextDouble();
    int hurt=0;
    if (Rate > 0.5)
    {
    hurt = 2 * HurtHP;
    Console.WriteLine("武器{0}出了暴击,攻击掉血{1}", Name, hurt);
    }
    else
    {
    hurt = HurtHP;
    Console.WriteLine("武器{0}出了普通攻击,攻击掉血{1}", Name, hurt);
    }
    monster.DecreaseHP(hurt);
    }
    public Random RandomRate { get; set; }
    public string Name { get; set; }
    public bool IsDouble { get; set; }
    public double Rate { get; set; }
    public int HurtHP { get; set; }
    }
    public class Monster
    {
    public Monster(string name, int hp)
    {
    _name = name;
    _hP = hp;
    _isDead = false;
    Console.WriteLine("怪物的姓名:{0},血量:{1},状态:{2}", Name , HP, IsDead ? "死亡" : "健康");
    }
    private string _name;

    public string Name
    {
    get { return _name; }
    set { _name = value; }
    }
    private int _hP;

    public int HP
    {
    get { return _hP; }
    set
    {
    if (value <= 0)
    _isDead = true;
    _hP = value;
    }
    }
    private bool _isDead;

    public bool IsDead
    {
    get { return _isDead; }
    set { _isDead = value; }
    }

    public void DecreaseHP(int lostHp)
    {
    this._hP -= lostHp;
    if (_hP <= 0)
    { this._isDead = true; }
    Console.WriteLine("怪物的姓名:{0},血量:{1},状态:{2}", Name, HP, IsDead ? "死亡" : "健康");
    }
    }
    }
     回复 引用 查看   
  68. #115楼 virus      2010-06-03 18:42
    Weapon weapon = new Weapon("铁剑", 100);
    Monster monster = new Monster("大BOSS", 100000);
    Role role = new Role("法师", weapon);

    while (monster.IsDead==false )
    {
    role.AttackMonster(monster);
    }
     回复 引用 查看   
  69. #116楼 virus      2010-06-03 18:53
    public interface IAttackStrategy
    {
    void AttackTarget(MonsterDemo monster);
    }
    public class RoleDemo
    {
    public RoleDemo(string name, IAttackStrategy weapon)
    {
    this.Name = name;
    this.Weapon = weapon;
    }
    public string Name { get; set; }
    public IAttackStrategy Weapon { get; set; }
    public void AttackMonster(MonsterDemo monster)
    {
    Console.WriteLine("角色{0}准备攻击怪物{1}", Name, monster.Name);
    this.Weapon.AttackTarget (monster);
    Console.WriteLine("角色{0}攻击怪物{1}结束", Name, monster.Name);
    }
    }
    public class WoodSword:IAttackStrategy
    {
    #region IAttackStrategy Members

    public void AttackTarget(MonsterDemo monster)
    { Rate = RandomRate.NextDouble();
    int hurt = 0;
    if (Rate > 0.5)
    {
    hurt = 2 * HurtHP;
    Console.WriteLine("武器{0}出了暴击,攻击掉血{1}", Name, hurt);
    }
    else
    {
    hurt = HurtHP;
    Console.WriteLine("武器{0}出了普通攻击,攻击掉血{1}", Name, hurt);
    }
    monster.DecreaseHP(hurt);

    }

    #endregion

    public WoodSword(string name, int hurtHp)
    {
    this.Name = name;
    this.HurtHP = hurtHp;
    RandomRate = new Random(DateTime.Now.Millisecond);
    }

    public Random RandomRate { get; set; }
    public string Name { get; set; }
    public bool IsDouble { get; set; }
    public double Rate { get; set; }
    public int HurtHP { get; set; }
    }
    public class MonsterDemo
    {
    public MonsterDemo(string name, int hp)
    {
    _name = name;
    _hP = hp;
    _isDead = false;
    Console.WriteLine("怪物的姓名:{0},血量:{1},状态:{2}", Name, HP, IsDead ? "死亡" : "健康");
    }
    private string _name;

    public string Name
    {
    get { return _name; }
    set { _name = value; }
    }
    private int _hP;

    public int HP
    {
    get { return _hP; }
    set
    {
    if (value <= 0)
    _isDead = true;
    _hP = value;
    }
    }
    private bool _isDead;

    public bool IsDead
    {
    get { return _isDead; }
    set { _isDead = value; }
    }

    public void DecreaseHP(int lostHp)
    {
    if (_hP <= 0)
    Console.WriteLine("怪物的姓名:{0},血量:{1},状态:{2}", Name, HP, IsDead ? "死亡" : "健康");
    this._hP -= lostHp;
    if (_hP <= 0)
    { this._isDead = true; }
    Console.WriteLine("怪物的姓名:{0},血量:{1},状态:{2}", Name, HP, IsDead ? "死亡" : "健康");
    }
    }


    IAttackStrategy woodSword = new WoodSword("木剑", 200);
    MonsterDemo demo = new MonsterDemo("小怪", 2000);
    RoleDemo roledemo = new RoleDemo("战士", woodSword);
    while (demo.IsDead == false)
    roledemo.AttackMonster(demo);

     回复 引用 查看   
  70. #117楼 virus      2010-06-03 18:54
    将容易变化的道具抽象为接口,角色就省了很多事情,只要实现接口的道具,原来的代码都可以使用,客户端不用修改,好啊,学习了
     回复 引用 查看   
  71. #118楼 virus      2010-06-03 18:57
    Peter和小于听后都很满意,认为小李总结的非常出色。

    小李总结的出色,可是架构是小于设计的,小李来总结, 明显的国内气息很严重啊,小于呢?埋没了??

    哈哈
     回复 引用 查看   
  72. #119楼 virus      2010-06-03 19:16
    public RoleDemo(string name, IAttackStrategy weapon)
    {
    this.Name = name;
    this.Weapon = weapon;
    }


    我这个应该算是构造注入吧
     回复 引用 查看   
  73. #120楼 Godot      2010-07-02 14:40
    最初的目的很单纯,就是为了偷懒,实现低耦,然后增加程序的扩展性!直到做了很久之后才知道这叫依赖注入。。我这种土鳖就只适合看楼主这样的文章。。。。术语少点,DEMO多点....

    我很土鳖 - -#
     回复 引用 查看   
  74. #121楼 virus      2010-07-03 15:54
    这个好像来自Head.First.Design.Pattern吧
     回复 引用 查看   
  75. #122楼 popzhou      2010-08-28 16:53
    楼主文笔风骚,学习了~
     回复 引用 查看   
  76. #123楼 不入流程序员      2010-09-20 10:18
    通俗移动 刀刀切入正题 总结完毕
     回复 引用 查看   
  77. #124楼 二锅头      2010-10-13 11:38
    好文章啊,看完后感觉自己以前写的代码都要改写了。!~
     回复 引用 查看   
  78. #125楼 Jonathan.yang      2010-10-13 13:21
    引用漫笔者:对楼主的钦佩犹如滔滔江水啊
    <br/>

    SAME
     回复 引用 查看   
  79. #126楼 leif      2010-10-13 15:02
    找了几篇关于依赖注入的,其它的看了几行就不想看了,唯独这篇从头看到尾啊,经典!
     回复 引用 查看   
  80. #127楼 skyaspnet      2010-10-21 14:07
    楼主的分享非常棒,很受用,向您学习
     回复 引用 查看   
  81. #128楼 skyaspnet      2010-10-21 14:15
    也就是说IOC和DI是有一定关系的?

    IOC是对DI的进一步延伸,可以这样理解吗?
     回复 引用 查看   
  82. #129楼 xxs      2011-01-08 13:26
    好文章呀!
     回复 引用 查看   
  83. #130楼 露水浮云--【濮蹄訄澱】      2011-02-16 11:32
    有一个问题,在反射工厂里有个问题:如果我现在要加入一个ITable接口里,如果按你的写法岂不是要更改反射工厂中的,先在XML中加入一个<Table>MacTable</Table>后,在反射工厂里你得再加入一个_tableType的ITable接口定义,在构造函数里根据XML加入一个ITable的注入点(读XML),然后你得在反射工厂里加入一个得到ITable的方法,这么看来,还是没有真正做到遵循OCP原则啊。
    请问老师,这个问题该如何解决啊?
     回复 引用 查看   
  84. #131楼 剑兰剑兰[未注册用户]2011-02-21 00:24
    看帖从来不回复,这次真是忍不住。牛逼。
     回复 引用   
  85. #132楼 北京美健宝科技公司      2011-02-25 21:57
    感谢楼主的文章,原来我也思考过这个问题,但是没有楼主这么深入,谢谢
     回复 引用 查看   
  86. #133楼 bluesev      2011-02-27 21:32
    貌似3.1.1 Setter注入的示例代码是构造注入的代码。
     回复 引用 查看   
  87. #134楼[楼主] T2噬菌体      2011-02-27 23:17
    引用bluesev:貌似3.1.1 Setter注入的示例代码是构造注入的代码。

    是Setter注入。。。
     回复 引用 查看   
  88. #135楼 yangkunlisi[未注册用户]2011-03-18 09:21
    @Old
    请问下,哪里贴错了?
     回复 引用   
  89. #136楼 李思      2011-03-18 10:31
    我是小李
     回复 引用 查看   
  90. #137楼 李思      2011-03-18 10:33
    而且还是实习生
     回复 引用 查看   
  91. #138楼 alexmen      2011-04-05 20:39
    很好很强大
     回复 引用 查看   
  92. #139楼 脚印      2011-04-17 17:04
    通俗易懂,好
     回复 引用 查看   
  93. #140楼 popzhou      2011-07-08 16:30
    怎一个好字了得.
     回复 引用 查看   
  94. #141楼 天行健 自强不息      2011-07-17 09:42
    非常好的文章
     回复 引用 查看   
  95. #142楼 Capt.Liu      2011-08-18 00:58
    这篇文章是我看过讲依赖注入最棒的文章,沁人心脾啊,赞!!!
     回复 引用 查看   
  96. #143楼 峰顶飞龙      2011-08-18 15:22
    看了两个多小时,终于看完了,看来本人水平实在有限。呵呵,受益匪浅,周末再亲自动手试试。感谢楼主的佳作!
     回复 引用 查看   
  97. #144楼 孤狼晖      2011-09-23 10:02
    写的太棒了!
     回复 引用 查看   
  98. #145楼 我每天都在进步o(∩_∩)o...      2011-10-07 17:27
    @Xuefly
    感觉还是用区别的:具体服务类没有必要知道任何客户类的信息,通俗的说就是我的服务已经按照契约(接口)准备好了,谁爱用谁用。很显然上面的代码中具体服务类跟客户类已经产生了耦合。
     回复 引用 查看   
  99. #146楼 wapjingle[未注册用户]2011-12-23 19:07
    通俗易懂,好文章
     回复 引用   
  100. #147楼 黑曜石      2012-01-07 15:53
    引用leif:找了几篇关于依赖注入的,其它的看了几行就不想看了,唯独这篇从头看到尾啊,经典!

    花了三四个小时拜读,很强大的一篇文章,感谢楼主!
     回复 引用 查看   
  101. 评论共2页: 上一页 1 2 
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1504693 C9yX2vI1/qY=