4

I am a beginner programmer and I got some class design problems. I want to have Load() method in the game class that is quite universal and polymorphic with dependency injection. Example follows:

Lets say I have a class:

public class MyGame {
   public IGameData _data;
   public ISolver _solver;
   public ILoader _loader;


   public MyGame(ILoader loader, ISolver solver) {
      _loader = loader; 
      _solver = solver;
   }

   public Load() {
      /*
      The loader might need a param or not, 
      how can I make the method Dependency Injection friendly
      */
       _data = _loader.Load();        
   }
}

The problem is that I would like to have FileLoader(), NetworkLoader() and etc. classes. Each of those requires different parameters. One solution I got is to pass the parameter when I am constructing the concrete Loader:

var fileLoader = new FileLoader('somegamestate.txt');
var someSolver = new SomeSolver();
var game = new MyGame(fileLoader, someSolver);

But such a design does not seem to work well with C# Unity dependency injection framework as I it is logically wrong to bind filename to FileLoader in the system as a whole.

I though on using factory, but that did not satisfy my soul :)

Do you have some ideas on the design and how it should be done?

Factory example:

public class MyGameFactory {
   public ISolver _solver;
   public MyGameFactory(ISolver solver){
       _solver = solver;
   }

   public MyGame MakeGame(ILoader loader) {
       return new MyGame(loader, _solver);
   }
}

P.S. I am coding in C#.

Zereges
  • 5,139
  • 1
  • 25
  • 49
  • I would avoid to have a parameter like a text file in the constructor. If you need the concrete file at some point, pass it as an argument to the method which requires it. – royalTS Jun 26 '18 at 07:32
  • Could you point me out where should it (the filename) be put? – user9992715 Jun 26 '18 at 07:42

3 Answers3

1

Following this:

You can use

container.RegisterType<ILoader , FileLoader>(
                new InjectionConstructor("somegamestate.txt") //Old way to pass value to constructor - not flexible. 
                );

or

container.Resolve<MyGame>(new DependencyOverride<ILoader >(new FileLoader("somegamestate.txt")));
Antoine V
  • 6,998
  • 2
  • 11
  • 34
  • As I see, `DependencyOverride` overrides dependencies in every object graph node and that might result in unexpected behavior. RegisterType has the same problem. I guess the only way would be to do property override. – user9992715 Jun 26 '18 at 09:16
1

One option for sure is to put the Load method in an interface and the implement the rest in other classes. That would allow you to always call it from the Game class but at the same time have each instance provide their own implementation.

The interface would be as simple as this:

public interface ILoader {
    void Load();
}

Here you inject the two strings your loader might need.

public class Loader1 : ILoader {
    public string prop1 { get; set; }
    public string prop2 { get; set; }

    public Loader1(string param1, string param2) {
        prop1 = param1;
        prop2 = param2;
    }

    public void Load() {
        // Use prop1 and prop2
    }
}

And in this case you inject some other object.

public class Loader2 : ILoader {
    public SomeObject prop1 { get; set; }

    public Loader2(SomeObject param1) {
        prop1 = param1;
    }

    public void Load() {
        // Use prop1
    }
}

The beauty of it is that each loader will get its own parameters via the constructor, but the game will always call the Load method in the same way. Were you to need a factory, then do it, but you can always use a common dependency injector, like unity, just like you mentioned.

Injecting parameters to constructors is feasible with most dependency injection containers. Just like @Thierry commented in his answer, you can do it like that with Unity. If, on the other hand, you prefer to switch to another framework which makes it slightly easier (opinion-based), you can try Ninject, for example. In that case, you would simply execute something like this:

Bind<ILoader>().To<FileLoader>().WithConstructorArgument("file.txt");

In any case, I think the design is more than fine.

Javier García Manzano
  • 1,024
  • 2
  • 12
  • 25
  • It looks like that you are paraphrasing my question text :) – user9992715 Jun 26 '18 at 08:39
  • @user9992715 true, I didn't notice rofl. TBH then, the design is more than fine. I'm not really familiarized with Unity container, but what you want to do is more than doable with most other injection frameworks, like ninject. I'll edit my answer with an example :) – Javier García Manzano Jun 26 '18 at 09:04
  • Same problem as other answers, what if I need to build more than one game object in the code? I do a Bind again? It looks like poor man solution... – user9992715 Jun 26 '18 at 09:29
  • @user9992715 well, I'm not 100% sure then what you're looking for. On the one hand, if it is just in order to not bind the FileLoader to a file name, use the `ConfigurationManager` in the constructor of the FileLoader. If you have the possible combinations of games to loaders constrained, then use options within the container (contextual binding). But if you want to achieve any combination of Loaders with Games, then maybe I would directly not use a container but inject them manually for any of the scenarios. In any case, if you do find the answer you're looking for, please do share it. – Javier García Manzano Jun 26 '18 at 10:03
0

Think about what are you trying to do. You want to load a game, hence you need some information about what game to load and this information is needed when loading. Therefore Load() needs a parameter that carries this information instead of the constructor of a ILoader implementation. Lets say the information is the number of the save slot then your class would look like this

public class MyGame {
   public MyGame(ILoader loader, ...) {
      _loader = loader;
   }

   public Load(int saveSlotNumber) {
       _data = _loader.Load(saveSlotNumber);        
   }
}

Now the ILoader implementation can translate the saveSlotNumber to whatever is suitable. If you have a FileLoader then it can load from {Application-Path}\savegames\{saveSlotNumber}.sav or if you have a NetworkLoader it can load from \\game-server\savegames\{saveSlotNumber}.sav.

ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
  • The solution you are proposing is good when you have a control of the software structure but that is not the case :/ – user9992715 Jun 26 '18 at 09:11