Proponents of dependency injection try to design classes so they can either work autonomously or get all services they rely on handed to them through their constructor. But even without dependency injection, the situation often arises where certain classes need to interact with a lot of other objects.
In these cases, you often end up with very complicated constructors and a lot of duplicate code:
public class RadarBuildingRenderer {
public RadarBuildingRenderer(
ISceneGraph sceneGraph,
IContentManager contentManager,
IAudioManager audioManager,
RadarBuilding building
) {
this.sceneGraph = sceneGraph;
this.contentManager = contentManager;
this.audioManager = audioManager;
this.building = building;
}
private ISceneGraph sceneGraph;
private IContentManager contentManager;
private IAudioManager audioManager;
private RadarBuilding building;
}
Above class takes care of rendering the visual and audible representations
of a RadarBuilding
in a computer game. As you can imagine,
the same references will be required by other buildings, think
TankFactoryBuilding
, CommandCenterBuilding
and so
on – all duplicating the fields, their assignment and the complex constructor.
So what any sane programmer would do is consolidate this code into a common
base class aptly named Renderer
:
public class Renderer {
public Renderer(
ISceneGraph sceneGraph,
IContentManager contentManager,
IAudioManager audioManager
) {
this.sceneGraph = sceneGraph;
this.contentManager = contentManager;
this.audioManager = audioManager;
}
protected ISceneGraph SceneGraph {
get { return this.sceneGraph; }
}
protected IContentManager ContentManager {
get { return this.contentManager; }
}
protected IAudioManager AudioManager {
get { return this.audioManager; }
}
private ISceneGraph sceneGraph;
private IContentManager contentManager;
private IAudioManager audioManager;
}
public class RadarBuildingRenderer : Renderer {
public RadarBuildingRenderer(
ISceneGraph sceneGraph,
IContentManager contentManager,
IAudioManager audioManager,
RadarBuilding building
) : base(sceneGraph, contentManager, audioManager) {
this.building = building;
}
private RadarBuilding building;
}
Now the code duplication is mostly taken care of, but we added another drawback to the code instead: if at a later time you notice that some buildings, like your FlameThrowerBuilding and that the ParticleBeamBuildiner, need to access the IParticleManagerService, adding this service to the root Renderer class requires you to change the constructors for all of the buildings you implemented so far. Not good.
The solution to this new problem is to use a reference container which turns those references into a single object, making the building classes resistant to change as long as the services they access are provided for (of course, this is not an invitation to randomly add references into the container – the principles of object oriented design still apply!)
public class RendererReferences {
public RendererReferences(
ISceneGraph sceneGraph,
IContentManager contentManager,
IAudioManager audioManager
) {
this.sceneGraph = sceneGraph;
this.contentManager = contentManager;
this.audioManager = audioManager;
}
public ISceneGraph SceneGraph;
public IContentManager ContentManager;
public IAudioManager AudioManager;
}
public class Renderer {
public Renderer(RendererReferences references) {
this.sceneGraph = references.SceneGraph;
this.contentManager = references.ContentManager;
this.audioManager = references.AudioManager;
}
protected ISceneGraph SceneGraph {
get { return this.sceneGraph; }
}
protected IContentManager ContentManager {
get { return this.contentManager; }
}
protected IAudioManager AudioManager {
get { return this.audioManager; }
}
private ISceneGraph sceneGraph;
private IContentManager contentManager;
private IAudioManager audioManager;
}
public class RadarBuildingRenderer : Renderer {
public RadarBuildingRenderer(
RendererReferences references, RadarBuilding building
) : base(references) {
this.building = building;
}
private RadarBuilding building;
}
For the price of a base class and a container class being added to the project, the constructors are simple once again and changing the references provided to a renderer can be done in a single place, without the need to edit all the renderer classes.
Needless to say that this pattern should only be applied if you have a lot of similar objects consuming the same services. In cases where there are only few variants required, a plain swithc statement or a set of methods might be a better option!