这些静态变量的可能替代方案是什么?

4

我创建了一个简单的GUI引擎,打算在游戏中使用。我的问题是如何实例化一个类,使其可以在多个堆栈帧中访问而不是静态的(人们已经明确表示静态变量只是邪恶的)。下面是我的代码示例:

class MyGame
{
    class InputEngine
    {
        internal void DoInput()
        {
            if (Keys["F1"].IsPressed)
            {
                // Create a window using the gui engine
            }
        }
    }
    class GuiEngine
    {
        internal void Update() { }
        internal void Draw() { }
    }
    private GuiEngine engine;
    private InputEngine input;

    internal MyGame()
    {
        this.input = new InputEngine();
        this.engine = new GuiEngine();
    }

    internal void Update()
    {
        this.engine.Update();
        this.input.DoInput();
    }
    internal void Draw()
    {
        this.engine.Draw();
    }
}

如何在不将其设为静态的情况下,从数十个其他位置访问GUI引擎实例。 (我真的不想将其作为参数传递)。


谢谢大家提供非常有用的答案!你们给了我很多思考的空间,这正是我要做的。我会去喝杯咖啡,看看如何实现这些建议。 - Tony
9个回答

2

依赖注入是你的好朋友。如前所述,通过构造函数(似乎是典型方式)或属性注入来公开依赖项。在任何情况下,在应用程序启动时注册依赖项时,您可以告诉容器将其作为单例进行处理,因此当您请求引用时,您会获得与静态引用相同的效果。

对于Ninject,语法如下:

var kernel = new Ninject.StandardKernel(settings);
kernel.Bind<ISomeInterface>().To<MyConcreteObject>().InSingletonScope();

对于Unity,语法如下:

UnityContainer container = new UnityContainer();
container.RegisterType<ISomeInterface, MyConcreteObject>(new ContainerControlledLifetimeManager());

因此,你的类可以是这样的:
public class MyThing
{
    ISomeInterface _mySingletonObject;

    public MyThing(ISomeInterface mySingletonObject)
    {
        _mySingletonObject = mySingletonObject;
    }
}

只要您使用容器解析该类的实例,这个类将始终获得相同的对象实例注入。

对于Ninject:

var singletopnObject = kernel.Get<ISomeInterface>();

我记得是从Unity(一个游戏引擎)中得到的

var singletopnObject = container.Resolve<ISomeInterface>();

所有其他IoC容器以不同的方式提供相同的功能。

P.S. 静态变量并不邪恶。当使用得当时,它们是快速且非常有用的。


2
您的问题的解决方案可能是重新设计。当您需要从数十个其他位置访问GUI引擎实例时,存在某些问题。这表明有很多依赖关系可能是错误的。

1

也许你可以在游戏引擎类型中实现单例模式? 如果你不会创建超过一个对象实例,我认为这是理想的选择。

否则,你可以尝试使用IoC解决方案,例如微软的Unity


就像简单地使用静态字段一样糟糕,而他正试图避免这种情况。 - CodesInChaos

1

你只有三个真正的传递选项 -

  • 静态
  • 参数化(cctor,方法等)
  • 委托/闭包/Lambda

像控制反转容器这样的技术也可以在创建、管理实例的生命周期和发现方面提供帮助。加入依赖注入,整个生命周期可以自动化,最小化归因/编码。

在这三种技术中,静态和参数化在明显性和简单性方面通过软件维护“测试”最好。IoC相当可读。然而,DI代码库往往感觉更像黑魔法,具有隐含的行为寿命、位置和绑定。

委托、闭包等可以很好地工作,创建时保留上下文,但调试可能会有点噩梦,经常维护/阅读闭包代码感觉混乱,因为它很少在编写它的上下文中执行。

如你所指出的,静态可能是最棘手的,更难以模拟、替换等。


0
你可以将它作为构造函数参数传递,而不是方法参数,例如:
public class InputEngine
{
    public InputEngine(IGuiEngine guiEngine)
    {
         // set private member to store guiEngine
    }
}

请注意,我还将GuiEngine更改为接口 - 这样您就可以使用依赖注入容器(例如StructureMap)根据需要自动传递GUI引擎给您的实例。这也有助于测试。
或者,您可以使用DI容器或工厂类来提供GUI引擎的单例实例。

0
Game的实例传递到其他类的构造函数中并将其保存在本地变量中。这样,您就可以从几乎任何地方访问Game及其所有成员,但仍然可以拥有多个游戏实例。
您可以通过使用依赖注入来自动化此过程。
在某些情况下,线程静态变量是一种选择,但我不认为您的情况是其中之一。

0

你可以使用IoC框架并使用依赖注入将输入引擎放置在需要的位置。或者,您可以使用“服务定位器”模式。


0
这项技术被称为依赖注入。实际上,您正在将“静态变量”注入到实例中。就实例而言,它是一个普通的实例变量。您只需将GUI的实例传递给构造函数即可。

0

关于静态变量是邪恶的第一件事情,在我看来并不是静态函数、单例类本身是邪恶的。但是,如果你想为使用静态上下文的类创建一些自动化测试,那几乎是不可能的。

因此,如果你想完全忽略创建自动化测试的希望,那么请继续使用具有静态数据的静态类。

然而,如果你想保持这个选项的开放性,那么我建议你研究像IOC容器(structuremap是我最喜欢的)这样的东西。你可以拥有一个(咳咳)静态的IOC容器,用它来创建你的依赖项实例。依赖项将以构造函数参数的形式接受一个实例,例如GuiEngine。你的IOC容器将确保所有依赖项接收相同的实例,假设你已经正确配置了它。

正如Tim所指出的,为你的引擎类提取一个接口是一个好主意,因为它将允许你为这些测试创建替代实现,例如模拟、存根、间谍、虚拟对象等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接