|
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!
|
Comments
I've never looked into the performance cost of this, but I'd rather have a class that include all my parameters than having to include tons of them.
Makes for cleaner code too.
RSS feed for comments to this post