模板和依赖注入

Templates and dependency injection

本文关键字:注入 依赖      更新时间:2023-10-16

我有一个类模板ResourceManager,它的目的是像这样使用:

ResourceManager<Image>* rm =
    ResourceManager<Image>::Instance();
Image* img = rm->acquire("picture.jpg");
rm->release(img);

我想使用依赖注入(将ResourceManager作为参数传递给应该使用它而不是全局使用它的函数),但是考虑到它是一个模板,我不知道如何做到这一点。你有什么建议吗?

我的游戏才刚刚开始开发,我已经有了四种资源类型(Image, Font, AnimationSound),所以为每种类型的资源制作一个具有获取功能的单一ResourceManager(即不是模板)不是一个选择。


编辑:一些澄清。

我要找的不是如何为一种类型的ResourceManager做依赖注入,而是一次为所有这些。

我的GameState对象需要在初始化/打开时加载资源;他们通过ResourceManagers这样做。然而,GameStates可能需要加载任何类型的资源:动画、字体、图像、声音等。每种ResourceManager都有很多函数参数!你建议我怎么做?

如果函数需要特定类型的资源(可能大多数都需要),只需将特定的模板实例定义为参数:

function(ResourceManager<Image> *rm, ...);

如果函数需要任何类型的资源,它可以

  1. 模板本身,如:

    template <typename T>
    function(ResourceManager<T> *rm, ...);
    

    它可能需要引用从资源管理器获得的资源,因此无论如何它将在更多的地方需要template参数。

  2. 使用多态基类。这就意味着你必须定义像

    这样的东西
    class ResourceManagerBase { /* methods you need to call via the base class */ };
    template <typename T>
    class ResourceManager : ResourceManagerBase { ... };
    function(ResourceManagerBase *rm, ...)
    

    函数可以调用基类中定义的任何方法。如果方法内部依赖于资源类型,它们将在基类中声明为抽象虚函数(virtual returnType method(...) = 0),并在模板类本身中定义。您还可以使用dynamic_cast来检查您获得了ResourceManager的哪个特定实例化。

    请注意,如果函数需要引用资源,您将同样需要所有资源的ResourceBase抽象基类,以便您可以引用任何类型的资源。

选择是在使用模板函数的更快但非常大的代码(函数将为每个专门化单独编译)或使用虚拟方法的更慢但更小的代码(调用虚拟方法更慢,但没有代码重复)之间进行权衡。此外,模板变体将会编译得更慢,因为大多数编译器会为使用它的每个对象文件生成代码,然后在链接时合并相同的副本。

第一。记住,在使用模板时,必须在编译时解决所有问题。

那么你的代码中的ResourceManager似乎是一个单例。粗略地说,对于全局变量,没有什么区别。

我认为当你可以直接调用单例时,传递rm作为函数的参数是没有用的。

我希望这就解决了你的问题。

使用构造函数注入的例子:

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 ...
}

注意,我已经忽略了所有的资源管理;

即使在您的编辑之后,如果没有看到整个图片,我也很难理解您想要的是什么,但我将在一个基本示例中进行第二次尝试:

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();
}

注释:我很清楚资源泄漏,但这不是这里的重点。