依赖注入和抽象

发表于2016-04-19
评论0 3.1k浏览

 

原文地址:http://blogs.unity3d.com/2014/05/07/dependency-injection-and-abstractions/

原文作者未做版权声明,视为共享知识产权进入公共领域,自动获得授权

对于任何软件产品来说,可测试性都是一个重要的功能,游戏开发也不例外。要拥有可测试性,所有组件都应该是独立的并且可以隔离测试。

当我们要独立测试某个东西意味着我们需要对它进行解耦。松耦合是我们需要的。在游戏开发中很容易出现隐藏的松散耦合,并且很难打破它。这篇文章将帮助你理解松散耦合以及Unity项目的依赖诸如,使用了github上的一个例子。

让我们以处理输入为例。


public class SpaceshipMotor : MonoBehaviour
{
void MoveHorizontally ()
{
var horizontal = Input.GetAxis ("Horizontal");
// ...
}
}

MoveHorizontally方法使用了静态Unity API(输入类)而没有告诉你。它认为这是它的私有事务,你不改控制或者影响它。它使得SpaceshipMotor与静态Unity API紧密耦合起来,如果不按下键盘上的键,你无法验证SpaceshipMotor的行为。

 

现在让我们来解决这个问题。

SpaceshipMotor只使用了水平轴,所以我们定义了一个需要何种输入的简单描述。


public interface IUserInputProxy
{
float GetAxis(string axisName);
}

现在你已经用我们的抽象来进行真实的调用。


public class SpaceshipMotor : MonoBehaviour
{
public IUserInputProxy UserInputProxy {get;set;}

void MoveHorizontally ()
{
var horizontal = UserInputProxy.GetAxis (“Horizontal”);
// …
}
}

现在是你控制整个态势了。除非你提供一个IUserInputProxy的实现,没法操作这个类。

http://blogs.unity3d.com/wp-content/uploads/2014/05/di-blog-image-2.png

 

这被成为依赖注入。当依赖(在我们的例子中是输入)被传递给依赖的物体(SpaceshipMotor类)并且变成状态的一部分(在我们的例子中是一个变量)。

传递依赖有几个选项:构造函数注入、属性注入、方法注入。

构造函数注入被认为是最受欢迎和最可靠的方法,因为依赖是在构建阶段传入的,未初始化的概率最低。

public class SpaceshipMotor : MonoBehaviour
{
private readonly IUserInputProxy userInputProxy;

public SpaceshipMotor(IUserInputProxy userInputProxy)
{
this.userInputProxy = userInputProxy;
}
}

但是Unity引擎负责调用MonoBehaviours的初始化,我们没有办法控制这个过程。

不过,属性和方法注入都是可用的。

最容易的手动诸如方法是使用脚本进行依赖注入。

 “Growing Games Guided by Tests”我们使用接口来导出属性依赖。

 


public interface IRequireUserInput
{
IUserInputProxy InputProxy { get; set;}
}

建一个允许设置假输入参数的脚本,并且在测试开始的时候把它注入进去。
public class ArrangeFakeUserInput : MonoBehaviour
{
public GameObject Spaceship;
public FakeUserInput FakeInput;

void Start () {
var components = Spaceship.GetComponents ();
var dependents = components.Where(c=>c is IRequireUserInput)
.Cast();
foreach(var dependent in dependents)
dependents.InputProxy = FakeInput;
}
}

这对于可测试性有什么贡献?

 

我们在“Growing GamesGuided by Test”里面有很多例子,通过帮助脚本注入一个输入,它会帮助我们测试行为。

另一方面,我们可以写一些依赖于抽闲的单元测试类。

 


[Test]
public void ChangesStateToIsFiringOnFire1ButtnPressed()
{
// Arrange
// Setting test double for user input
IUserInputProxy userInput = Substitute.For ();
// Telling GetButton method of test double to return true
// if state of “Fire1” was requested
userInput.GetButton(Arg.Is("Fire1")).Returns (true);
// Passing the dependency to Gun object on creation
Gun gun = new Gun(userInput);
// Act
gun.ProcessInput ();
// Assert
Assert.That(gun.IsFiring, Is.True);
}

 

现在你可以看到其实依赖注入并不神秘。就是一个替换具体依赖并且让它依赖一个外部抽象的过程。

 

如果你要在一个大规模的项目中使用依赖注入,你需要一个自动化工具,这将是我们下一篇文章的主题。

 

 

 

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引