开发者

What is a possible alternative to these static variables?

I have created a simple GUI engine which I plan to use in a game. The issue I am having is understanding how to instance a class that will be accessed in multiple stack frames without being static (people have made it quite clear that static variables are just evil).

I have a code example below:

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();
开发者_StackOverflow    }
}

How can I access the gui engine instance not only from the input example, but from tens of other places without making it static. (I would really rather not pass it as a parameter).


The solution to your problem might be redesign. There is something wrong when you need to

access the gui engine instance not only from the input example, but from tens of other places

This indicates lots of dependencies that might be just wrong.


Dependency injection is your friend. As already stated, expose the dependency via the constructor (which seems to be the typical way) or you could do it via Property injection. At any rate, when you register your dependency at application startup, you can tell the container to make it a singleton so when you ask for a reference, you get the same effect as a static reference.

For Ninject, the syntax is:

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

For Unity, the syntax is:

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

So your classes can be something like:

public class MyThing
{
    ISomeInterface _mySingletonObject;

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

This class will always get the same instance of the object injected into it, provided you use the container to resolve an instance of that class.

Again, for Ninject:

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

And unity (I think from memory)

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

And all other IoC containers offer the same features in different ways.

P.S. Statics are not evil. They are fast and very useful when used properly.


You've only got three real passing options -

  • Statics
  • Parameterization (cctor, method, etc.)
  • Delegates/Closures/Lambda

Techniques like inversion of control containers can help here too in terms of creating, managing-lifetime-of, and discovering instances. Add in dependency injection and the whole lifecycle can be automatic with minimal attribution/coding.

Of the three techniques, statics and parameterization pass the software maintenance "test" best in terms of obviousness and simplicity. IoC is fairly readable. However DI codebases tend to feel a lot more like black-magic with implicit behaviour for lifetime, location, and binding.

Delegates, closures, etc. can work very well, with context preserved when created however debugging can be a bit of nightmare, and often maintaining/reading closure code feels jumbled since it is rarely executed in context of where it is written.

Statics are as you point out probably most gnarly, being harder to mock, replace etc..


Perhaps you could implement the singleton pattern in your game engine type? If you're not ever creating more than 1 object instances of it, I think this is the ideal choice.

Otherwise you could try using an IoC solution, like Mincrosoft's Unity.


Pass the instance of Game into the constructor of the other classes and save it in a local variable. That way you can access Game and all its members from almost anywhere, but still can have multiple instances of game.

You can automate that by using Dependency Injection.

In some situation thread-static variables are an alternative, but I don't think yours is one of those.


You can use an IoC framework and use dependency injection to have the input engine where you need. Alternativeli you can use the "service locator" pattern.


The technique is called dependency injection. In effect you are "injecting" the "static variable" into the instance. As far as the instance is concerned it's a normal instance variable. You simply pass your instance of the gui to the constructor.


You could pass it as a constructor parameter, rather than a method parameter, e.g.:

public class InputEngine
{
    public InputEngine(IGuiEngine guiEngine)
    {
         // set private member to store guiEngine
    }
}

Note I've also changed the GuiEngine to an interface - this way you can use a Dependency Injection container, such as StructureMap to automatically pass the GUI engine to your instances as required. Helps with testing too.

Alternatively, you could use a DI container, or a factory class, to provide a singleton instance of the GUI Engine.


Now the first thing about statics being evil is IMHO not that the static function, singleton classes themselves are evil. But if you wanted to create some automated tests for classes that use your static context, that would be virtually impossible.

So if you want to complete disregard any hopes of creating automated tests, then go ahead, make the class static with static data.

Should you however want to keep this option open, then I would suggest that you look into stuff like IOC containers (structuremap being my favourite). You can have a (ahem) static IOC container which you use to create instances of your dependencies. The dependencies would take an instance of the, e.g. GuiEngine, as a constructor argument. And your IOC container will make sure that all dependencies receive the same instance, assuming that you've configured it correctly.

As Tim points out, extracting an interface for your engine classes would be a good idea, as it will allow you to create alternate implementations for said tests, e.g. mock, stub, spy, dummy objects etc.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜