trangeIoC是一个专门为c#和Unity3D编写的控制反转(IoC)微框架。Strange是小型的、快速的、高度可扩展的,并且是为在项目变得更大、更复杂时变得更有用而专门构建的。
框架只是一个工具,如果你希望一个框架能干所有的事情,但现实中是没有的。具体如何用如何理解需要自己不断的总结和重构,随着自己能力的提高会提升自己的编码和设计能力。
目标
本次介绍Strange控制反转的核心原则,以及Strange是如何实现这些原则的。
Strange作者自己的一些想法
就像Strange框架开发者所说Strange是一个可以被称为" opinionated framework"(固执的框架)。“Opinionated的意思是它假定做事情有正确和错误的方法,“Framework” 意味着Strange不仅仅是一个库或实用程序。它不是给你一个工具而是一个工具集。框架的意义在于指导您以“正确”的方式完成某些任务。Unity3D自身底层也是这样。
以与其观点目标一致的方式使用固执己见的框架通常可以解决大量问题。对于Unity和Strange来说,这当然是正确的。试图使用一个框架,而不同意它的观点,往往会导致更多的困难。
在这篇指南中,你会发现一些例子,在这些例子中,Strange的观点与Unity的观点发生了碰撞。在我看来,这很好。这意味着我的应用程序的绝大部分都是结构良好的,并且异常大多以一致的方式处理。
依赖注入
StrangeIoC中的“IoC”代表控制反转,依赖项注入是与该概念最密切相关的机制。那么什么是“控制”?“依赖”是什么?我们求的到底是什么?我们要注射什么?
先举个例子,下面就是一个依赖的例子,ShipModel依赖外部类 LaserGun,我们通过获取Gun的实例来调用他的公开的方法。
public class ShipModel
{
public LaserGun Gun { get; set; }
public void Attack()
{
Gun.Fire();
}
}
但是我们要是没有Gun的实例,那么程序就会报空指针异常。
我们常用的方法是新建一个对象 如下所示
public class ShipModel
{
private LaserGun Gun = new LaserGun();
public void Attack()
{
Gun.Fire();
}
}
毫无疑问,你已经做过很多次了这样的操作。这是一个很有吸引力的选项,因为它似乎消除了外部依赖并提供了封装。其实并不是这样,这是个坏主意。为什么呢?
这就是紧密耦合的本质。通过在内部定义ShipModel所依赖的具体类,我们立即排除了这种关系的所有可能性。如果我们想要手枪而不是激光枪呢?如果我们想让其他班级知道枪的事呢?如果我们想要在整个应用程序中共享一个枪的实例呢?还是两个实例?
您的类所依赖的任何东西都是依赖项。将其设为私有并在内部定义它并不会消除依赖性,而只是将其隐藏起来。您希望您的依赖项是可访问的和可更改的,这样您就可以尽可能地保持灵活性。
对于这个问题有许多建议的解决方案。你可能知道他们中的一些(public setter、Signletons、factories)。Strange(以及类似的框架)的前提是通过注入来满足依赖性是正确的解决方案。
public class ShipModel
{
[Inject]
public IGun Gun { get; set; }
public void Attack()
{
Gun.Fire();
}
}
在上面的例子中,您可能从未见过的代码位是[Inject]标记。如果你忽略它,你会发现我所做的只是定义了一个普通的公共getter/setter。
添加[Inject]标记表明这个类有一个需要填充的依赖项。我将在接下来的几段中向您展示具体的方法。
public interface IGun
{
void Fire(); //An interface defines API, not implementation
}
public class LaserGun : IGun
{
public void Fire()
{
Debug.Log(“滋滋滋!”); //Implementation is in the concrete class
}
}
public class BBGun : IGun
{
public void Fire()
{
Debug.Log(“Biu Biu Biu!”);
}
}
public class WaterPistol : IGun
{
public void Fire()
{
Debug.Log(“哗哗哗!”);
}
}
public class SuperGun : IGun
{
public void Fire()
{
Debug.Log(“灭霸打了个响指!”);
}
}
所以我们在看之前的ShipModel类,我有一个枪械库,开始我想用水枪,过会我想换个武器,用用激光枪。最后我还想试试超级无敌枪——灭霸的响指。如果IGun是一个单例的话会怎么赋值呢?每次换武器的时候重新赋值一个枪吗?现在就展现出单例的缺点了。当然单例在某些情况下是很有用的。但是单例使用多了会增加你的项目依赖程度,后期很难维护和修改。
public class ShipModel
{
[Inject]
public IGun Gun { get; set; }
public void Attack()
{
Gun.Fire();
}
}
在Strange中就可以这样去注入枪的依赖,不关心具体实现类。这可能看起来很简单,因为我们的示例很简单。但是想象一下这些依赖的几十个,成百上千个。每个具体的依赖项都会稍微降低代码的灵活性。您的应用程序越大,您就越需要管理这个不断扩大的依赖圈。
那么我们需要在哪里进行绑定操作呢?答案是Context
injectionBinder.Bind<IGun>().To<WaterGun>();
上面这样的绑定会在每次注入的时候都会生成一个WaterGun的示例。
如果我们想在游戏中只有一个水枪实例怎么办呢?
injectionBinder.Bind<IGun>().To<WaterGun>().ToSingleton();
那么下面介绍一下Strange的整个流程
1.ContextView是一个MVCSContext的实例,它继承了MonoBehavior。
2.当在框架Start内发送信号给Command,框架进行初始化。
3.Mediators通过派发信号来与Command进行通讯。
4a.Command派发信号给Mediator
4b.Command对service和Model的API进行逻辑等操作。
5.Services通过command来对Model内的数据进行更新和设置。
6.Models和Services可能会给Mediators派发一些消息。
Views只跟Mediators进行通讯,View只处理跟Unity相关的操作,不要增加其他依赖。
明天跟大家分享一下Signal 和Command。