如何在模板成员函数工厂中隐藏注入的依赖项
我有一个拆分为多个子项目的C++项目,其中一个项目实现了通过模板成员函数注入依赖的工厂,示例代码如下:
class FooManager { public: // 其他业务代码 template<class TBar> TBar makeInstance() { return TBar(depA, depB); } private: DependencyA depA; DependencyB depB; };
当前的问题是,这种实现会迫使DependencyA和DependencyB成为FooManager对外接口的一部分(头文件必须包含这两个依赖的定义)。有没有办法保留模板成员函数作为工厂方法的同时,把注入的依赖类型从接口中完全隐藏?
补充说明:将depA和depB隐藏在Pimpl中是可选方案,对所有TBar类型进行显式模板特化也是可选方案。
可行解决方案
方案1:Pimpl惯用法(最常用的隐藏方式)
通过Pimpl(指针实现)将依赖类型封装到内部实现类中,FooManager的头文件只对外暴露一个指向实现类的指针,模板成员函数的实现移到.cpp文件中,同时对需要的TBar类型进行显式实例化。
头文件(FooManager.h):
class FooManagerImpl; class FooManager { public: FooManager(); ~FooManager(); // 必须在.cpp中定义,确保正确销毁Impl template<class TBar> TBar makeInstance(); private: FooManagerImpl* pImpl; };
实现文件(FooManager.cpp):
#include "FooManager.h" #include "DependencyA.h" #include "DependencyB.h" #include "FooBar.h" // 假设需要实例化的TBar类型 #include "Baz.h" // 内部实现类,持有实际依赖 class FooManagerImpl { public: DependencyA depA; DependencyB depB; }; FooManager::FooManager() : pImpl(new FooManagerImpl()) {} FooManager::~FooManager() { delete pImpl; } // 模板函数的实现 template<class TBar> TBar FooManager::makeInstance() { return TBar(pImpl->depA, pImpl->depB); } // 显式实例化所有需要的TBar类型,避免模板链接错误 template FooBar FooManager::makeInstance<FooBar>(); template Baz FooManager::makeInstance<Baz>();
这种方式下,FooManager的头文件完全不需要暴露DependencyA和DependencyB,只有实现文件需要依赖它们。
方案2:显式模板特化
如果TBar的类型是有限且已知的,可以为每个TBar显式特化makeInstance函数,将特化实现放在.cpp文件中,头文件仅声明特化版本。
头文件(FooManager.h):
class FooManager { public: // 声明通用模板 template<class TBar> TBar makeInstance(); }; // 声明需要的显式特化(供调用方使用) template<> FooBar FooManager::makeInstance<FooBar>(); template<> Baz FooManager::makeInstance<Baz>();
实现文件(FooManager.cpp):
#include "FooManager.h" #include "DependencyA.h" #include "DependencyB.h" #include "FooBar.h" #include "Baz.h" // 持有依赖(可以用Pimpl进一步封装,这里简化) DependencyA depA; DependencyB depB; // 实现FooBar的特化 template<> FooBar FooManager::makeInstance<FooBar>() { return FooBar(depA, depB); } // 实现Baz的特化 template<> Baz FooManager::makeInstance<Baz>() { return Baz(depA, depB); }
这种方式下,头文件不需要包含依赖类型的定义,仅需声明TBar类型即可。
方案3:类型擦除封装依赖提供者
定义一个抽象的依赖提供者接口,FooManager持有该接口的指针,内部实现类持有具体依赖。模板函数通过接口获取依赖,避免直接暴露依赖类型。
头文件(FooManager.h):
// 抽象依赖提供者 class DependencyProvider { public: virtual ~DependencyProvider() = default; virtual void* getDepA() = 0; virtual void* getDepB() = 0; }; class FooManager { public: FooManager(); ~FooManager(); template<class TBar> TBar makeInstance() { // 通过接口获取依赖并转换类型 auto depA = static_cast<DependencyA*>(provider->getDepA()); auto depB = static_cast<DependencyB*>(provider->getDepB()); return TBar(*depA, *depB); } private: DependencyProvider* provider; };
实现文件(FooManager.cpp):
#include "FooManager.h" #include "DependencyA.h" #include "DependencyB.h" // 具体依赖提供者 class ConcreteProvider : public DependencyProvider { public: DependencyA depA; DependencyB depB; void* getDepA() override { return &depA; } void* getDepB() override { return &depB; } }; FooManager::FooManager() : provider(new ConcreteProvider()) {} FooManager::~FooManager() { delete provider; } // 显式实例化需要的TBar类型 template FooBar FooManager::makeInstance<FooBar>();
注意:这种方式头文件仍需声明DependencyA和DependencyB的类型(用于类型转换),如果想完全隐藏,需要让TBar的构造函数接受抽象依赖接口,但会增加TBar的耦合度。
内容的提问来源于stack exchange,提问作者RAM

