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?
6
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.
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.
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.
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.
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:
- Increased testability
- Ability to switch out implementations or use multiple implementations
- 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.