Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have a bunch of classes that look something like this:

public class MyGame()
{
    private Graphics graphics;

    private Player player;

    public MyGame()
    {
        graphics = new Graphics();
        //...
    }

    protected void Initialize()
    {
        player = new Player();
        //...
    }

    protected void Update()
    {
        player.DoStuff();
        //...
    }

I've been reading about design patterns, and I've come to understand that this is not a very good design because it isn't loosely coupled. Instead of coding to interfaces, I'm coding to implementations (Graphics, Player, etc.).

In order to fix this perceived problem, I start by changing those dependencies to interfaces:

public class MyGame()
{
    private IGraphics graphics;

    private IPlayer player;

    public MyGame()
    {
        graphics = new Graphics();
        //...
    }

    protected void Initialize()
    {
        player = new Player();
        //...
    }

I'm not sure how helpful that really is, but it's nice that the graphics field (for example) isn't tied to the Graphics class - I can use the AwesomeGraphics class or SparklyGraphics class instead as long as those classes implement IGraphics.

But I still have hard dependencies on Graphics and Player, so what was the point of all that refactoring? (My actual code is much more complicated.) I've also come to understand that dependency injection is a good idea, so that's what I try to do next:

public class MyGame()
{
    private IGraphics graphics;

    private IPlayer player;

    public MyGame( IGraphics graphics, IPlayer player )
    {
        this.graphics = graphics;
        this.player = player;
        //...
    }

This looks really nice because I no longer have any hard dependencies in MyGame. However, the dependencies are still in my code - they've just been moved to the class that instantiates MyGame:

static class Program
{
    static void Main( string[] args )
    {
        using ( var game = new Game( new Graphics(), new Player() )
        {
            game.Run();
        }
    }
}

Now I'm back where I started! This class is not loosely coupled at all! So, again, what was the point of all that refactoring? I'm pretty sure I could use an IoC container to take care of these dependencies somehow, but that would likely just add a ton of complication to an already complicated project. Plus, wouldn't that just be moving those dependencies once again to another class?

Should I stick with what I had in the first place, or is there some tangible benefit that I'm not seeing yet?

share|improve this question

migrated from codereview.stackexchange.com Dec 31 '13 at 17:31

This question came from our site for peer programmer code reviews.

    
Please retag this with the relevant language. –  Jamal Dec 31 '13 at 7:03
    
Looks like C# so I tagged is as such –  ChrisWue Dec 31 '13 at 7:12
    
You can do whatever you like if your toy project would never leave the IDE, where building, testing, deployment, configuration, migration etc procedures are either non-existent or keyboard shortcuts. Even then you should train like you would fight, because you will fight like you train. –  abuzittin gillifirca Dec 31 '13 at 10:02
1  
Program.Main is now your Composition Root, which is what you want if you don't use an IoC. –  Facio Ratio Dec 31 '13 at 19:01
    
Thanks for that. I had no idea what composition root was, and you mentioning led me down a new path of discovery. –  Koveras Dec 31 '13 at 20:07

5 Answers 5

You have gained the benefits of loose coupling in your refactoring. You've moved the knowledge of the precise implementation outside of your MyGame class and into a higher-level class. This is good: by keeping all knowledge of what the precise implementations are together, you're improving the structure of your code, making it easier for high level changes to take place, and making it easier to make changes that involve, for example, using multiple implementations of your Player class (perhaps by introducing a NetworkPlayer or AIPlayer later). MyGame now has one fewer reason to change, so is closer to having a single responsibility, and the Single Responsibility Principle is now considered one of the most important in object-oriented design by many programmers.

share|improve this answer

Eventually, all code has to depend on a concrete implementation, whether that's specified in code or some sort of DI framework configuration file. The benefit of depending on an interface is that you can more easily depend on multiple concrete implementations, as the situation calls for.

However, those benefits aren't actually realized until you actually have multiple concrete implementations. Most frequently, the second concrete implementation is a mock object used in unit testing. It could also be things like multiple target platforms. For example, I have a project that depends on interfaces for the UI so I can substitute concrete implementations for either Swing or Android. It's common to have interfaces for multiple databases or operating system-specific functionality.

If you don't ever have that second implementation, you aren't actually receiving any tangible benefit. You just have a future assurance that if you ever need a second implementation, it will be easier to integrate. It's like owning non-dividend stock. It's worth something on paper, but you don't get any real benefit until you actually sell it.

share|improve this answer

You certainly have gained substantial benefit from changing the structure.

When coding new systems or fairly stable systems implementing design patterns can seem like a waste of effort and time since it didn't make your life easier right now.

Consider that the Graphics object you were using originally was developed for rendering 2D graphics and does all kinds of logic under the covers to make development easier. Later on your game grows and now needs to start doing some 3D graphics, normally you would start by inheriting from Graphics, only 2D graphics while it may have some similar methods such as Render(), Refresh(), or Invalidate() has nothing to do with 3D graphics.

By opening up several SIMPLE interfaces for Graphics you open the door to much more flexibility in your code.

As an example of the strategy pattern (dependency inversion) and coding to interfaces:

MyGame requires several things to function.

It needs our Render(), Refresh(), Invalidate(). (IGraphics) It needs additional 3D methods such as Transform(), Rotate() . (IGraphicsManipulations3D)

And if those multiple interfaces make sense to be lumped together for something you can make a combined interface

public interface IGraphics3D : IGraphics, IGraphicsManipulations3D

so now instead of saying you need a SPECIFIC graphics implementation you just need some object that does the basic things, you could even swap in completely different rendering engines if you wanted to.

public class UnityEngineGraphics : IGraphics3D

or

public class HavokEngineGraphics : IGraphics3D

But your game doesn't care one bit.

public class MyGame(IGraphics3D, IPlayer)

The important thing to note and most likely where you find it difficult to see value is that these circumstances tend to come up later rather than sooner and when you are building the MyGame you are fully intent on using one implementation of the graphics object. Never forget that requirements change, sometimes for good, sometimes for headaches and design patterns are THE way to be preemptive.

share|improve this answer

In this example:

public MyGame( IGraphics graphics, IPlayer player )

the statement:

However, the dependencies are still in my code

is not correct. The are no dependencies because now the class is tied to a contract and not an implementation.

In some cases it is not necessary to be loosely coupled. For example, the player class may not be a good candidate to be loosely coupled if it just contains properties or core logic.

Let's take a look at a better example, let's say your game uses a 3rd party ranking engine to rank players.

So your constructor may look like this:

public MyGame( IRankingProvider Provider )

Now you can swap out different implementations of a ranking provider rather easily, your code may have 3 or 4 implementations depending on what platform the game is running on.

Also, for unit testing you can create a fake Provider to unit test the logic on MyGame without any external ranking provider dependencies. This makes it easy to run all the unit tests on a development/build machine without installing/setting up any of the providers.

Now eventually on a development/deployment server one would want to perform integration tests using actual providers to ensure the implementation are correct, in those cases the test fixture would inject a real implementation.

So, the main benefits are:

  1. Increased testability
  2. Ability to switch out implementations or use multiple implementations
  3. Removal of dependencies

One should choose carefully which parts of the system are loosely coupled. In some cases it is not needed and when employed causes a needless layer of abstraction.

share|improve this answer

At the program level, there will always be a point at which a decision needs to be made which concrete implementations of IGraphics and IPlayer will be given to Game. This is what you're seeing here.

The point of your work is that you've moved the decision out of Game itself, so it isn't tightly coupled to any particular implementations, and left it to your higher level program code to decide.

This means that Game is much more flexible (as are IGraphics, and IPlayer), and your program is now made up of freely composable units instead of one inflexible hierarchical unit.

It may not seem like a big deal with such a simple contrived example, but once you get even a little bit more complex, the composability becomes much more beneficial.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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