2

The antipattern-plagued game I'm creating is using ASP.NET Core, which, in many cases, requires the use of dependency injection. This is new and counter-intuitive to me. Up to now, I managed to limit the use of dependency injection to where it is strictly required by the framework, while writing the mechanics of the game in the "my" old way, that is, with a lot of explicit object creations (var blah = new SomeClass(arg1, arg2) etc.) (And without creating interfaces when only one class would implement them.)

Reading a bit about dependency injection, which the framework's documentation recommeds to use everywhere, I was perplexed to find out that I should not call constructors nor explicitely create any objects, because instead, I should request all objects I would like ClassA to ever create in the constructor of ClassA and let the framework provide them for me!

Skimming over my code, in many cases I can't see how could I accomplish this. Simply moving all news to the constructor doesn't seem to work.

One example is when it seems to be runtime dependant on which objects should be created.

To be more specific, let me provide an example for this example ;P There are many Moves characters can use:

public class Punch : Move
{
    // ...
}

public class SwordSlash : Move
{
    // ...
}

etc.

The client (web browser) will typically send the server a command with a name of the move the player would like to execute. Omitting the many necessary validations (for simplicity) this is the code of the relevant method of the server:

var moveType = System.Type.GetType("Game.Mechanics."+moveTypeName);
if (!(moveType.BaseType == typeof(Move))) return; // OK one of the many validations I did not omit
move = (Move)Activator.CreateInstance(moveType, args);

That's right - the server just looks if there is a class whose name is equivalent to the string sent by the client. Well, dependency injection violation?

So how should I do this instead?

The only way that comes to my mind is to have a class whose contructor will accept ALL (currently 79 and counting) moves and that will have a method with a gargantuan switch to select a proper move:

public class MoveSelector : IMoveSelector
{
    private readonly IPunch punch;
    private readonly ISwordSlash swordSlash;
    // 77 more privates

    public MoveSelector (
        IPunch punch,
        ISwordSlash swordSlash,
        // 77 more args
    ) {
        this.punch = punch;
        this.swordSlash = swordSlash;
        // 77 more assignments
    }

    public IMove SelectMove(string moveName)
    {
        switch(moveName)
        {
            case "Punch":
                return punch;
            case "SwordSlash":
                return swordSlash;
            // 77 more cases
        }
    }
}

I somehow feel this is not the right way to go. Especially since adding a new move would now get rather tedious.

So how should I fix my code so that I don't violate the rule that I'm supposed to avoid the new keyword?

4
  • Instead of creating 77 privates of specific types, why don't you just create one IMove private?. Commented Sep 13, 2018 at 20:31
  • @RobertHarvey Because how can I decide what type it gets?
    – gaazkam
    Commented Sep 13, 2018 at 20:32
  • 1
    In a switch statement. You return a custom object using return new whatever() in each case. It's a classic example of a Factory Method pattern. You don't actually need any local variables to make it work, unless of course you feel like holding onto the resulting IMove object locally. Commented Sep 13, 2018 at 20:42
  • Some programming languages will allow you to instantiate a type directly using a string name, such as C# as shown here. Commented Sep 14, 2018 at 0:20

3 Answers 3

3

Dependency injection is not to be used without thinking and definitely not everywhere.

In modern application development, dependency injection is pretty much necessary for service classes (beans), i.e. those classes for which it only really makes sense to have one instance across your entire application. Examples of those would be the dependency injection container itself, repository instances, perhaps HTTP client,...

But obviously you are allowed to call the new from time to time. If some object represents an instance of something and is not a service class and not a service class dependent, you can instantiate it directly and it's completely fine.

If you have an class of which an instance is runtime dependent and this class depends on a service object, instantiating such objects is usually done using a factory (where the factory is a service class), e.g. like this:

class MyClassDependendOnServiceClass
{
    public MyClassDependendOnServiceClass(userInput: UserInput, service: Service)
    {
        // ...
    }
}

class MyClassDependendOnServiceClassFactory
{
    private Service service;

    public MyClassDependendOnServiceClassFactory(service: Service)
    {
        this.service = service;
    }

    public MyClassDependendOnServiceClass Create(userInput: UserInput)
    {
        return new MyClassDependendOnServiceClass(userInput, this.service);
    }
}

You then inject the factory instance into e.g. a controller and obtain a MyClassDependendOnServiceClass object using it.

If your moves do not depend on anything like this and are supposed to be created each time, simply do so. There's no problem with that.

1

That's right - the server just looks if there is a class whose name is equivalent to the string sent by the client. Well, dependency injection violation?

Nope. Thats fine. It's basically what spring does. They just do it with a xml file.

But please don't confuse framework dependency injection with pure dependency injection.

Framework DI helps you keep construction separate from use by making you construct in a different language. In springs case this other language is often XML. In your case it's simple strings.

Pure DI lets you stick with your main general purpose language. You just have to learn how to keep use and construction separate on your own.

That means breaking the habit of constructing, or going and finding something, the moment you realize you need it. Instead you announce that you depend on it by asking it to be passed to you. This makes your needs explicit. It makes them easier to spot since no one has to crawl the code to learn what it needs.

Construct objects as high up the call stack as you can. That doesn't mean everything is built in main. Just everything that lives as long as main. That isn't everything. For example, it is ok to construct now timestamps when they are needed because they need info that didn't exist until just now.

Notice I said nothing about when the timestamp is used. All I care about is whether I have what I need to know to construct it. Don't let use drive construction. Let the availability of info that construction depends on drive construction.

0

I'll just focus on one point, and hopefully, this will help you understand the pattern better.

"the server just looks if there is a class whose name is equivalent to the string sent by the client. Well, dependency injection violation?"

Note that we are talking about an application boundary here - a network boundary to be precise. That basically means that you should consider the client and the server as two distinct (although related) peaces of software, communicating over a network by exchanging data.

You don't do cross-bondary dependency injection - that's stretching the metaphor too far (or at least, it's not as useful to stretch it that far in this context).

On the server side (the entire discussion below refers to the server side), your dependency is the code that receives and processes the data that arrived over the network (which a form of input), in the sense that there is some higher-level code that implements some logic and depends on the input-processing code.

You invert that dependency by introducing an abstraction that represents the dependency. E.g., you may introduce an interface, and then (1) make your dependency implement your interface, and (2) change your higher-level code where necessary so that all logic is written against that interface. Then you can also do dependency injection (into the higher-level code) by passing in (preferably through the constructor) an instance that implements the interface.

Another important point: The concrete move types are just a part of your internal model on the server. They are part of the core of the code (in enterprise architecture, you'd say they are a part of the domain model). That's the heart of your (server) application that everything else (within that application) ultimately depends on. The game logic is written in terms of these core classes. You don't inject that from the outside. You inject the stuff that's at the application boundaries - the outer layers, stuff that supports your main logic but isn't a core part of it.

Note that dependency inversion and dependency injection are two distinct things, but can be and often are combined together.

Although you can apply dependency inversion together with dependency injection in many parts of your application, it's across the boundaries of the logical layers within the application where it's really important. It controls the dependency (coupling) directions of layers and subsystems within your application. You can apply it within the layers themselves, but it's less of a problem if you don't. You also have to have some idea about how you want to layer your application. If we are going to take a clue from the IO/pure separation in functional programming, there should be at least two layers - the Application Logic, and the "Gateways" layer towards various external data sinks and sources. In the image below, I've depicted a 3-layer design (not counting the "Libraries, Frameworks" layer).

(enlarge)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.