Reputation: 31577
I have a class template ResourceManager
and it is intended to be used something like this:
ResourceManager<Image>* rm =
ResourceManager<Image>::Instance();
Image* img = rm->acquire("picture.jpg");
rm->release(img);
I'd like to use dependency injection (pass the ResourceManager
as a parameter to functions that are supposed to use it instead of having it used globally), however given that it's a template I don't know how to do this. Do you have any suggestions?
My game is only at the beginning of the development and I already have four resource types (Image
, Font
, Animation
and Sound
) so making a single ResourceManager
(i.e. not a template) with an acquire function for each type of resource is not an option.
Edit: Some clarifications.
What I'm looking for is not how to do dependency injection for one type of ResourceManager, but for all of them at once.
My GameState
objects need to load resources when they are initialized/opened; they do so through the ResourceManagers
. However, GameStates
may need to load any number of types of resources: animations, fonts, images, sounds, etc — that's a lot of function parameters for each kind of ResourceManager
! What do you suggest I do?
Upvotes: 0
Views: 1048
Reputation: 2343
Even after your edit I find it a little hard to understand what you're after without seeing the whole picture, but I'll make a second attempt at a basic example:
template<typename T>
struct ResourceManager
{
virtual T* acquire(std::string const& resourceName)
{ return ...; }
// ... etc ...
};
class ImageUser
{
ResourceManager<Image>* rm_;
public:
explicit ImageUser(ResourceManager<Image>* rm)
: rm_(rm)
{}
void UseImage()
{
Image* img = rm_->acquire("picture.jpg");
rm_->release(img);
}
};
template<typename T>
struct ResourceManagerFake : ResourceManager<T>
{
T* acquireRetVal;
virtual T* acquire(std::string const& resourceName)
{ return acquireRetVal; }
// ... etc ...
};
void test_imageuser()
{
ResourceManagerFake<Image>* rm = new ResourceManagerFake<Image>;
rm->acquireRetVal = new Image;
ImageUser iu(rm);
iu.UseImage();
}
Note to commenters: I'm well aware of the resource leaks, but that's not the point here.
Upvotes: 0
Reputation: 2343
An example using constructor injection:
template<typename T>
struct ResourceManager
{
virtual T* acquire(std::string const& resourceName)
{ return ...; }
... etc ...
};
class ImageUser
{
ResourceManager<Image>* rm_;
public:
explicit ImageUser(ResourceManager<Image>* rm)
: rm_(rm)
{}
ImageUser()
: rm_(ResourceManager<Image>::Instance())
{}
void UseImage()
{
Image* img = rm_->acquire("picture.jpg");
rm_->release(img);
}
};
struct ImageResourceManagerFake : ResourceManager<Image>
{
virtual Image* acquire(std::string const& resourceName) // override
{ return <whatever-you-want>; }
... etc ...
};
void test_imageuser()
{
ImageResourceManagerFake* rm = new ImageResourceManagerFake;
ImageUser iu(rm);
... test away ...
}
Note that I've left out all resource management; use smart pointers etc wherever applicable.
Upvotes: 0
Reputation: 76296
Well, if the function needs a particular kind of resource (most will, probably), just define the specific template instance as parameter:
function(ResourceManager<Image> *rm, ...);
If the function needs any kind of resource than it can either
Be a template itself, like:
template <typename T>
function(ResourceManager<T> *rm, ...);
It will probably need to refer to the resource obtained from resource manager, so it will need the template argument in more places anyway.
Use a polymorphic base class. That would mean you'd have to define something like
class ResourceManagerBase { /* methods you need to call via the base class */ };
template <typename T>
class ResourceManager : ResourceManagerBase { ... };
function(ResourceManagerBase *rm, ...)
The function can call any methods defined in the base class. If the methods depend on resource-type internally, they will be declared abstract virtual (virtual returnType method(...) = 0
) in the base and defined in the template class itself. You can also use dynamic_cast
to check which particular instantiation of ResourceManager
you've got.
Note, that if the function needs to refer to the resource, you will similarly need a ResourceBase
abstract base class of all resources, so you can refer to any kind of resource.
The choice is matter of trade-off between faster but very large code with template function (the function will be compiled for each specialization separately) or slower but smaller code with virtual methods (call to virtual method is slower, but there is no code duplication). Also the template variant will compile slower, because most compilers will generate the code for each object file that uses it and than merge identical copies at link time.
Upvotes: 1
Reputation: 39
First. Remember when you use templates, you must resolve everything at compile time.
Then ResourceManager in your code seems a singleton. So there's no difference, rougly speaking, with a global variable.
And I think that is useless pass rm as parameters of your functions when you can call the singleton directly.
And this resolves, I hope, your question.
Upvotes: 0