依赖注入
场景出发
假设存在如下游戏场景:
1:角色可以装备木剑,铁剑,魔剑3种装备,分别对怪物造成20HP,50HP,100HP伤害(未佩戴装备则无法攻击);
2角色可以向怪物攻击,一次攻击后损失角色所佩戴装备的HP伤害,当HP损失完毕后,怪物死亡;
不假思索地我会写出如下的代码:
1 class Monster 2 { 3 public string Name { get; set; } 4 public int HP { get; set; } 5 /// <summary> 6 /// 怪物被攻击后提示 7 /// </summary> 8 /// <param name="loss">武器造成的HP伤害损失</param> 9 public void Warn(int loss) 10 { 11 if (HP <= 0) 12 { 13 Console.WriteLine($"怪物{Name}已经死亡"); 14 return; 15 } 16 17 HP -= loss; 18 19 Console.WriteLine($"怪物{Name}受到{loss}HP伤害"); 20 21 if (HP <= 0) 22 { 23 Console.WriteLine($"怪物{Name}被打死了"); 24 } 25 } 26 }
1 class Role 2 { 3 public string Name { get; set; } 4 public string Weapon { get; set; } 5 /// <summary> 6 /// 武器攻击 7 /// </summary> 8 /// <param name="monster">攻击的怪物对象</param> 9 public void Attack(Monster monster) 10 { 11 if (Weapon == "WoodenSword") 12 { 13 Console.WriteLine($"{Name}用木剑攻击了{monster.Name}"); 14 monster.Warn(25); 15 } 16 17 else if (Weapon == "IronSword") 18 { 19 Console.WriteLine($"{Name}用铁剑攻击了{monster.Name}"); 20 monster.Warn(50); 21 } 22 else if (Weapon == "MagicSword") 23 { 24 Console.WriteLine($"{Name}用魔剑攻击了{monster.Name}"); 25 monster.Warn(100); 26 } 27 else 28 { 29 Console.WriteLine($"{Name}没有武器,无法攻击"); 30 } 31 } 32 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var monster = new Monster() 6 { 7 Name = "沼泽首领", 8 HP = 80 9 }; 10 var role = new Role() 11 { 12 Name = "狂战士", 13 Weapon="IronSword" 14 }; 15 role.Attack(monster); 16 role.Weapon = "WoodenSword"; 17 role.Attack(monster); 18 role.Weapon = "MagicSword"; 19 role.Attack(monster); 20 21 Console.ReadKey(); 22 } 23 }
相信不止我一个人会这样写,因为它能快速的"完美的"实现上述功能
回过头来再仔细观察这段代码,就感觉像在看一段"直肠子",所有的逻辑算法都集中到了一个管道上,只要有需求或逻辑上的的变化,那么就得直接去修改业务类
改进分析
分析上述代码,它严重违背了开闭原则(OCP,Open Closed Principle,即我们的程序是可扩展的,在遇到新的需求或者变化的时候,是不需要对原有类有任何修改的),显然上述代码无法适应新的需求.随便一点需求的变化,就必须去修改具体类,对此我们做出如下简单的修改:
将Weapon抽象出来, 使Role对Weapon的依赖,转为对Weapon抽象的依赖,代码如下
1 public interface IWeaponStrategy 2 { 3 void WeaponAttack(Monster monster); 4 }
1 public class WoodenSwordStrategy : IWeaponStrategy 2 { 3 public void WeaponAttack(Monster monster) 4 { 5 Console.WriteLine("木剑攻击"); 6 monster.Warn(20); 7 } 8 } 9 public class IronSwordStrategy : IWeaponStrategy 10 { 11 public void WeaponAttack(Monster monster) 12 { 13 Console.WriteLine("铁剑攻击"); 14 monster.Warn(50); 15 } 16 } 17 18 public class MagicSwordStrategy : IWeaponStrategy 19 { 20 public void WeaponAttack(Monster monster) 21 { 22 Console.WriteLine("魔剑攻击"); 23 monster.Warn(100); 24 } 25 }
1 public class Monster 2 { 3 public string Name { get; set; } 4 public int HP { get; set; } 5 6 /// <summary> 7 /// 怪物被攻击后提示 8 /// </summary> 9 /// <param name="loss">武器造成的HP伤害损失</param> 10 public void Warn(int loss) 11 { 12 if (HP <= 0) 13 { 14 Console.WriteLine($"怪物{Name}已经死亡"); 15 return; 16 } 17 18 HP -= loss; 19 20 Console.WriteLine($"怪物{Name}受到{loss}HP伤害"); 21 22 if (HP <= 0) 23 { 24 Console.WriteLine($"怪物{Name}被打死了"); 25 } 26 } 27 }
1 class Role 2 { 3 public string Name { get; set; } 4 public IWeaponStrategy Weapon { get; set; } 5 public void Attack(Monster monster) 6 { 7 Weapon.WeaponAttack(monster); 8 } 9 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var monster = new Monster() 6 { 7 Name = "沼泽首领", 8 HP = 80 9 }; 10 var role = new Role() 11 { 12 Name = "狂战士", 13 Weapon=new IronSwordStrategy() 14 }; 15 role.Attack(monster); 16 role.Weapon = new WoodenSwordStrategy(); 17 role.Attack(monster); 18 role.Weapon = new MagicSwordStrategy(); 19 role.Attack(monster); 20 21 Console.ReadLine(); 22 } 23 }
依赖注入
依赖注入(Dependency Injection,DI)即控制反转(Inversion of Control,IoC)[初步的理解,本质上是有区别的]:
起因:客户类或上端)(Role)依赖了服务类(Weapon)的细节(实例);
目的:降低程序耦合度
方式:依赖抽象
本质:一个转移依赖的过程
我认为依赖注入就是一个转移依赖的过程,只是因为实现方式的不同,依赖最后转移到了不同的地方
依赖注入的方式
1Setter注入
在客户类中,添加一个服务类的抽象类型的数据成员,并添加一个需要服务类抽象类型的参数的方法做为注入点,在方法中把具体的服务类赋值给该数据成员
简单修改下客户类即为Setter注入
1 class Role 2 { 3 private IWeaponStrategy _weapon = null; 4 5 public string Name { get; set; } 6 7 public void AddWeapon(IWeaponStrategy weapon) 8 { 9 _weapon = weapon; 10 } 11 public void Attack(Monster monster) 12 { 13 _weapon.WeaponAttack(monster); 14 } 15 }
2构造注入
构造注入与Setter注入类似,所不同的是构造方法替代了上述添加的方法,如果上述事例改为构造注入,则只需要修改下客户类和上端代码即可
1 class Role 2 { 3 private IWeaponStrategy _weapon = null; 4 public Role(IWeaponStrategy weapon) 5 { 6 _weapon = weapon; 7 } 8 public string Name { get; set; } 9 10 public void Attack(Monster monster) 11 { 12 _weapon.WeaponAttack(monster); 13 } 14 }
在构造的时候把具体的服务类赋值给该数据成员
3配置文件
上述两种方式注入,在上端任然需要实例具体的服务类,我们可以通过配置文件加工厂模式,把依赖从上端转移到工厂内部
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <appSettings> 4 <add key="Weapon" value="WoodenSword"/> 5 </appSettings> 6 </configuration>
1 public class WeaponFactory 2 { 3 public static IWeaponStrategy GetWeapon() 4 { 5 6 var type = ConfigurationManager.AppSettings["Weapon"]; 7 8 if (type== "IronSword") 9 { 10 return new IronSwordStrategy(); 11 } 12 else if (type == "WoodenSword") 13 { 14 return new WoodenSwordStrategy(); 15 } 16 else 17 { 18 return new MagicSwordStrategy(); 19 } 20 } 21 }
如果在再配合反射,工厂内部的依赖也可以被转移
依赖注入的方式并不单一,要根据实际情况选择最合适的,但是它的本质是很容易理解的,在这个基础上,不管它如何的延伸,都可以轻松掌握
参考推荐博客:依赖注入那些事儿
出自:博客园-半路独行
原文地址:https://www.cnblogs.com/banluduxing/p/9185142.html
本文出自于http://www.cnblogs.com/banluduxing 转载请注明出处。