很久以前就接触过依赖注入,一直也没怎么搞明白那是个什么东西,这两天发现书上专门有一章讲依赖注入的也仔细看了下,写出来,边理解边记录。
为了更好地理解依赖注入,我们先来看一下设计模式中的控制反转模式。几乎每个人都编写过下面这段代码吧!
public class EmailService { public void SendMessage() { } } public class NoticeSystem { private EmailService svc; public NoticeSystem() { svc = new EmailService(); } public void NoticeHappened() { svc.SendMessage(); } }
这里NoticeSystem和EmailService是高耦合(一个类知道与它交互的类的大量信息)的,为了降低二者的耦合性,一般采取两个独立的步骤:
1>在两个代码块中间添加抽象层。下面的代码中绿色注释的是与前例不同的地方,主要任务是将NoticeSystem中的私有副本抽象成接口IMessageService,不再是具体的类型。 这里的IMessageService接口就是我们的依赖注入点.
//比上上述例子多了IMessageService接口 public interface IMessageService { void SendMessage(); } //比上上述例子多了IMessageService接口 public class EmailService : IMessageService //实现IMessageService接口 { public void SendMessage() { } } public class NoticeSystem { private IMessageService svc; //EmailService变成了接口IMessageService public NoticeSystem() { svc = new EmailService(); } public void NoticeHappened() { svc.SendMessage(); } }
2>将选择抽象实现的责任移到消费者类的外部。就是说将上述代码中EmailService的创建也移到NoticeSystem的外部去。
到这里我们就可以给控制反转下一个定义了:将依赖的创建移到使用这些依赖的类的外部,这称为控制反转模式,之所以这样命名,是因为反转的是依赖的创建,正因为如此,才消除了消费者类(NoticeSystem)对依赖创建的控制。让NoticeSystem类本身不能决定是创建EmailService、XXService还是XXXService。
要将svc=new EmailService();的创建从类NoticeSystem内部移出,有两种常见的方法:服务定位器和依赖注入。上面讲到IMessageService是依赖注入点,所以依赖注入通俗的理解就是为依赖注入点IMessageService创建对象。
服务定位器:
////// 服务定位器接口,GetMessageService方法用来返回一个IMessageService接口的对象 /// public interface IServiceLocator { IMessageService GetMessageService(); } ////// /// public class NoticeSystem { private IMessageService svc; //EmailService变成了接口IMessageService public NoticeSystem(IServiceLocator locator) { svc = locator.GetMessageService(); } public void NoticeHappened() { svc.SendMessage(); } } ////// 实现了 IServiceLocator接口的类EmailLocator /// 返回实现了 IMessageService接口的类EmailService /// public class EmailLocator : IServiceLocator { public IMessageService GetMessageService() { return new EmailService(); } }
下图对上述代码做了一个补充,帮助我们来理解服务定位器。可以看到,强类型服务定位器有两个弊端:<1>如果我们为IMessageService又添加了一种服务后,服务定位器IServiceLocator也就要再添加一个类,来返回IMessageService新添加的类对象。<2>从图中我们可以清晰地看到EmailLocator返回的是EmailService对象,PhoneLocator返回的是PhoneService对象,但是这是建立在我们知道的基础上,那如果我把返回对象写反了,那即使返回了错误的对象也没人知道吧!所以有一种叫泛型的东西就出现了,这就是下面要说的的弱类型服务定位器。
弱类型服务器看得一头雾水,正在研究中。
直接说依赖注入吧。
有了上面的基础,依赖注入就不难理解了。在使用服务定位器的情况下,我们要通过接口IServiceLocator来创建IMessageService对象。现在我们抛弃了IServiceLocator,他会很伤心的,但是我们真的不需要他了,他是多余的。我们直接用IMessageService对象来替代IServiceLocator的位置,就OK了。
这个就是构造函数注入,通过NoticeSystem的构造函数对依赖注入点IMessageService svc创建依赖对象。
public class NoticeSystem { private IMessageService svc; //EmailService变成了接口IMessageService public NoticeSystem(IMessageService service) { this.svc = service; } public void NoticeHappened() { svc.SendMessage();
还有就是属性注入,通过对属性赋值,实现依赖注入。
public class NoticeSystem { public IMessageService svc { get; set; } public void NoticeHappened() { svc.SendMessage(); } }
现在通过一个例子来看一下依赖注入的应用,有篇文章叫《依赖注入那些事》,借用下你的素材啊,不会告我侵权吧!......
现在要实现下面这么个打怪功能,看需求
说实话,我第一次做的时候真做的不怎么样。还是分析的不到位,现在一起来分析一下吧。
首先:需求中的对象有什么?角色、武器、怪物
然后:要求是什么呢? 1>角色可以装备不同的武器 2>不同的武器有不同的伤害 3>角色攻击怪物,怪物失血,没血了就死亡了。
最后:分析一下这里面的对象与要实现的方法的关系,我一开始就是没搞清那个对象该实现哪个方法,最终代码写的一塌糊涂。
1>角色装备武器,这个是角色与武器的关系,根据前面讲的依赖注入,可以放在角色的构造函数中实现。
2>不同的武器有不同的伤害,这个肯定是在武器类内部实现的,与角色没什么关系,角色只需要知道自己装备的是什么武器就可以了。但是与怪物又有些关系,因为装备不同的武器,怪物每次掉血是不一样的。
3>角色、攻击、怪物,这三个分开来看,这里的“攻击”应该是角色的一个方法吧!而“怪物”是“角色”攻击的对象,应该是可变的,作为一个参数吧!因为我们打完小喽啰还要打大BOSS的啊,要不谁玩这个游戏呢?
4>怪物失血,“失血”这个动作在哪里实现呢?应该是怪物这个对象失血,所以应该在怪物类内部实现,我一开始就写错了。
角色:
////// 角色 /// public class Person { ////// 武器接口 /// private IWeapon weapon; ////// 构造函数 /// /// public Person(IWeapon weapon) { this.weapon = weapon; } ////// 打怪方法 /// /// public void Attrack(Monster monster) { weapon.Attack(monster); } }
武器:(这里为了方便写在了一起,实际写的时候还是建新类比较好)
////// 武器接口 /// public interface IWeapon { void Attack(Monster monster); } ////// 木剑 /// public class WoodSword : IWeapon { public void Attack(Monster monster) { monster.LessBlood(20); } } ////// 铁剑 /// public class IronSword : IWeapon { public void Attack(Monster monster) { monster.LessBlood(50); } } ////// 魔剑 /// public class MagicSword : IWeapon { private Random _random = new Random(); public void Attack(Monster monster) { int loss = (_random.NextDouble() < 0.5) ? 100 : 200; if (loss == 200) Console.WriteLine("出现暴击!!"); monster.LessBlood(loss); } }
怪物:
////// 怪物 /// public class Monster { ////// 怪物名字 /// public string Name { get; set; } ////// 怪物血量 /// public int Blud { get; set; } ////// 构造函数 /// /// /// public Monster(string name, int blud) { this.Name = name; this.Blud = blud; } ////// 怪物掉血 /// /// public void LessBlood(int less) { while (this.Blud > 0) { this.Blud -= less; Console.WriteLine(this.Name+"减去"+less+"血!"); } Console.WriteLine(this.Name+"已死!"); } }
到这里,依赖注入就算是学习完了。