[Unity 设计模式]IOC依赖倒置
1.前言
最近在看《游戏开发与设计模式》一书,看到控制反转设计模式,作者说:上层模块不应该依赖于下层模块,上层模块和下层模块都应该依赖于接口,这样能减少耦合。然后附带举了个例子,我觉得特别好,就是一台计算机是属于上层模块,里面硬盘属于下层模块,计算机依赖于硬盘,硬盘是计算机的基本组成部件之一。这里提到依赖一词,下面就详细谈谈依赖。
2.依赖
依赖就是一种联系关系,人对人的依赖那是一种羁绊关系。再拿上面的计算机举例,华硕是我们都耳熟能详的计算机厂商,西部数据和希捷都是硬盘厂商,如果说华硕依赖于某一个具体型号的硬盘,那么华硕同一款型号的电脑那得生产多少台内置硬盘不同的型号的电脑。假设华硕厂商喜欢用西部数据的硬盘,那好假如西部数据倒闭或者硬盘升级,那么华硕厂商是不是会造成连锁反应,华硕已经生产的电脑要重新回炉重造或者说要被迫改成使用其他希捷厂商的电脑。造成这种问题的原因就是华硕对西部数据有严重的依赖关系,解决方法就是硬盘厂商生产硬盘的时候遵循硬盘接口SATA接口协议,然后计算机厂商也遵循这种接口协议,这就是我们看到的PC具有好多接口例如我们熟知的USB接口,USB是更通用的接口,无论鼠标、键盘还是优盘都具备这样的接口,所以只要遵循USB接口或者ASTA接口的设备都能用用在任何一个PC计算机上。我们如果写代码的话,也能这样设计我们的代码模块,那么模块跟模块之间耦合度就很低了。下面我们来看一个简单的例子:
1 public class FlashDisk 2 { 3 public string TypeName { get; set; } 4 public void TransportData() 5 { 6 Console.WriteLine(string.Format("读取{0}硬盘的数据")); 7 } 8 9 public FlashDisk(string name) 10 { 11 TypeName = name; 12 } 13 } 14 15 public class HardDisk 16 { 17 public string TypeName { get; set; } 18 public void TransportData() 19 { 20 Console.WriteLine(string.Format("读取{0}硬盘的数据")); 21 } 22 23 public HardDisk(string name) 24 { 25 TypeName = name; 26 } 27 } 28 public class Computer 29 { 30 public Computer(string name) 31 { 32 TypeName = name; 33 } 34 public void ReadHardDiskData(HardDisk m_HardDisk) 35 { 36 m_HardDisk.TransportData(); 37 } 38 39 public void ReadFlashDiskData(FlashDisk m_FlashDisk) 40 { 41 m_FlashDisk.TransportData(); 42 } 43 } 44 static void ReadData() 45 { 46 HardDisk hardDisk = new HardDisk("西部数据"); 47 FlashDisk flashDisk = new FlashDisk("闪迪"); 48 Computer pc = new Computer("联想"); 49 pc.ReadHardDiskData(hardDisk); 50 pc.ReadFlashDiskData(flashDisk); 51 }
上面是一个简单的示例,想要实现传输数据的功能,用户的操作一个PC来读取某个硬盘或者优盘才能实现。先看看依赖关系:
- Computer依赖HardWare
- ReadData操作依赖Computer
- ReadData操作依赖HardDisk/FlashDisk
3.依赖倒置
如果我们这时候用不用的电脑比如联想来读取金斯顿优盘的数据,我们就要抽象出来减少耦合。
耦合就是依赖,如果依赖过于严重就会牵一发而动全身,所谓城门失火,殃及池鱼。依赖关系越少,维护就越容易,所以必须要减少依赖。
幸亏Robert Martin提出面向对象设计原则-依赖倒置原则:
- 上层模块不应该依赖于下层模块,他们共同依赖于一个抽象。
- 抽象不依赖于具体,具体依赖于抽象。
理解:
- 上层模块就是使用者,这里的PC使用优盘、硬盘,而后者自然就是被使用者就是下层模块。他们应该都共同依赖于抽象——接口。
- 面向对象编程时需要面向抽象或者面向接口编程,抽象或者接口一般比较稳定,接口不要依赖于具体的对象。
根据控制反转的原则我们来重新设计上的实现:
- 优盘或者硬盘都要遵循一个接口协议,这里我们就定义ISATA和IUSB协议,
- 数据传输接口ISATA和IUSB都要遵循数据传输协议我们就定义ITransportData
- ReadData操作依赖PC,就让ReadData和HardDisk/FlashDisk都依赖ISATA和IUSB协议,于是优化过的代码就出来了
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using UnityEngine; 5 6 public interface ITransportData 7 { 8 void TransPortData(); 9 } 10 11 public interface IUSB : ITransportData 12 { 13 string TypenName { get; set; } 14 } 15 16 public interface ISATA : ITransportData 17 { 18 string TypenName { get; set; } 19 } 20 21 public class UFlashDisk : IUSB 22 { 23 public string TypenName { get; set; } 24 25 public UFlashDisk(string typeName) 26 { 27 TypenName = typeName; 28 } 29 public void TransPortData() 30 { 31 UnityEngine.Debug.Log(string.Format("优盘型号:{0}传输数据", TypenName)); 32 } 33 } 34 35 public class HardDisk : ISATA 36 { 37 public string TypenName { get; set; } 38 39 public HardDisk(string typeName) 40 { 41 TypenName = typeName; 42 } 43 44 public void TransPortData() 45 { 46 UnityEngine.Debug.Log(string.Format("硬盘型号:{0}传输数据", TypenName)); 47 } 48 } 49 50 public class Computer 51 { 52 ISATA m_HardDisk; 53 54 public void SetHardWare(ISATA hardDisk) 55 { 56 m_HardDisk = hardDisk; 57 } 58 59 public void ReadData() 60 { 61 if (m_HardDisk == null) 62 throw new Exception(); 63 m_HardDisk.TransPortData(); 64 } 65 66 public void UseUFlashDisk(IUSB flashDisk) 67 { 68 flashDisk.TransPortData(); 69 } 70 } 71 72 public class IOCDemo : MonoBehaviour 73 { 74 void Start() 75 { 76 ISATA hardDisk = new HardDisk("西部数据硬盘"); 77 IUSB uFlashDisk = new UFlashDisk("闪迪优盘"); 78 var pc = new Computer(); 79 pc.SetHardWare(hardDisk); 80 pc.ReadData(); 81 pc.UseUFlashDisk(uFlashDisk); 82 pc.UseUFlashDisk(new UFlashDisk("西门子优盘")); 83 } 84 }
4.控制反转(IOC)
现实生活中,我们想要读取数据,无论我们用什么电脑,文件数据在哪个优盘里,这都取决于我们用户自己。
上面基本实现了隔离,具体的电脑跟优盘隔离,具体的优盘、硬盘跟接口有关系,如果我们对象在代码里面写死,假如就用华硕电脑读取闪迪优盘,这样又不灵活了,如果需求是我用联想电脑去读取金斯顿优盘,我们是不是又要改代码。这样我们就可以把决定权交给我们配置的人,游戏开发中这类人就称之为策划,我们就把权利给他们,让他们想用什么电脑读取什么优盘就让他们来配表实现,然后程序写一个读表生成具体对象的读取数据的操作。
这样控制权交给了配表的文件,这种就是我们这里要说的控制反转。
1 public class Computer 2 { 3 public string Name { get; set; } 4 public Computer(string name) 5 { 6 Name = name; 7 } 8 9 ISATA m_HardDisk; 10 11 public void SetHardWare(ISATA hardDisk) 12 { 13 m_HardDisk = hardDisk; 14 } 15 16 public void ReadData() 17 { 18 if (m_HardDisk == null) 19 throw new Exception(); 20 m_HardDisk.TransPortData(); 21 } 22 23 public void UseUFlashDisk(IUSB flashDisk) 24 { 25 flashDisk.TransPortData(); 26 } 27 } 28 29 var pc = new Computer(“读取配置表”); 30 pc.SetHardWare(new UFlashDisk(“读取配置表”)); 31 pc.ReadData();
控制反转IOC是Inversion of control的缩写,说将对象进行转移,转移到第三方,比如交给了配表文件。它就像一个对象创建工程,想要什么对象就配置什么对象,这样原先的依赖关系没有了,都变成IOC容器依赖了。
5.依赖注入(ID)
上面说的控制反转是一个思想概念,要看具体的实现,配表也是一种实现方式。依赖注入提出了具体的思想。
依赖注入DI是Dependency Injection缩写,它提出了“哪些东东的控制权被反转了,被转移了?”,它也给出了答案:“依赖对象的创建获得被反转”。
所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。
上面SetHardWare其实就是运行时候依赖注入,将具体的依赖对象传递进去,传递的切入点可以是方法,可以是属性,可以是构造函数。
6.小结
通过一个小例子来由浅入深讲解IOC模式,看完之后记得自己在敲一遍哦,这样感悟才深刻!
7.工程下载
https://git.oschina.net/dingxiaowei/UnityIOC.git