C++ c++;依赖注入多态性
我有一个关于多态类依赖注入的最佳实践的问题。我是C++新手,请原谅我这是一个明显的问题。假设我有一个类运行程序,它需要接受两个对象,一个记录器和一个工作者。Logger是一个抽象类,有两个子类,比如FileLogger和SocketLogger。类似地,Worker是一个抽象类,有两个子类,比如ApproximateWorker和CompleteWorker Runner类将从main()创建,并将基于配置文件或类似文件创建记录器和工作程序。我在某处和其他地方读了很多书,一般的感觉似乎是更喜欢堆栈分配的对象并通过引用传递它们。但是,我不太确定如何动态地创建这样的对象。如果使用堆分配的对象,我可以执行以下操作:C++ c++;依赖注入多态性,c++,memory-management,dependency-injection,C++,Memory Management,Dependency Injection,我有一个关于多态类依赖注入的最佳实践的问题。我是C++新手,请原谅我这是一个明显的问题。假设我有一个类运行程序,它需要接受两个对象,一个记录器和一个工作者。Logger是一个抽象类,有两个子类,比如FileLogger和SocketLogger。类似地,Worker是一个抽象类,有两个子类,比如ApproximateWorker和CompleteWorker Runner类将从main()创建,并将基于配置文件或类似文件创建记录器和工作程序。我在某处和其他地方读了很多书,一般的感觉似乎是更喜欢堆
Logger* log;
Worker* worker;
if (/*user wants a file logger*/ {
log = new FileLogger();
} else {
log = new SocketLogger();
}
if (/* user wants an approximate worker*/) {
worker = new ApproximateWorker();
} else {
worker = new CompleteWorker();
}
Runner runner = Runner(log, worker);
runner.run();
if (/*file logger and approx worker*/) {
FileLogger log();
ApproximateWorker worker();
Runner runner = Runner(log, worker);
} else if (/*file logger and complete worker*/) {
FileLogger log();
CompleteWorker worker();
Runner runner = Runner(log, worker);
} else if (/*socket logger and approx worker*/) {
SocketLogger log();
ApproximateWorker worker();
Runner runner = Runner(log, worker);
} else {
SocketLogger log();
CompleteWorker worker();
Runner runner = Runner(log, worker);
}
因为我只是将指针存储在堆栈上,所以我可以独立地处理Logger和Worker的不同情况。如果使用堆栈分配的对象,我唯一能想到的是:
Logger* log;
Worker* worker;
if (/*user wants a file logger*/ {
log = new FileLogger();
} else {
log = new SocketLogger();
}
if (/* user wants an approximate worker*/) {
worker = new ApproximateWorker();
} else {
worker = new CompleteWorker();
}
Runner runner = Runner(log, worker);
runner.run();
if (/*file logger and approx worker*/) {
FileLogger log();
ApproximateWorker worker();
Runner runner = Runner(log, worker);
} else if (/*file logger and complete worker*/) {
FileLogger log();
CompleteWorker worker();
Runner runner = Runner(log, worker);
} else if (/*socket logger and approx worker*/) {
SocketLogger log();
ApproximateWorker worker();
Runner runner = Runner(log, worker);
} else {
SocketLogger log();
CompleteWorker worker();
Runner runner = Runner(log, worker);
}
显然,要传入两个以上的对象,或者每个对象有两个以上的子类,这很快就会变得荒谬。我的理解是,对象切片将阻止您执行与第一个片段类似的操作
我是不是漏掉了什么明显的东西?或者这是使用动态内存(当然是智能指针)的情况吗?如果
Runner
将以多态方式使用这些对象(通过基类接口访问派生对象),则应向其传递指针或引用。堆栈和堆上的变量各有利弊。没有一条普遍的规则规定一个优先于另一个
还有一件事,可能适合你的情况。它将(使用的对象的确切类型)与(使用这些对象的方式)区分开来。这一切都是关于封装更改
// Factory.h
class tAbstractFactory
{
public:
virtual Logger* getLogger() = 0;
virtual Worker* getWorker() = 0;
};
template<typename loggerClass, typename workerClass>
class tConcreteFactory: public tAbstractFactory
{
public:
loggerClass* getLogger() { return new loggerClass; }
workerClass* getWorker() { return new workerClass; }
};
// Runner.h
class Runner
{
public:
Runner(tAbstractFactory &fa)
{
m_logger = fa.getLogger();
m_worker = fa.getWorker();
}
private:
Logger *m_logger;
Worker *m_worker;
};
// Factory.cpp
tAbstractFactory &getFactory(int sel)
{
if (sel == 1)
{
static tConcreteFactory<FileLogger, ApproximateWorker> fa;
return fa;
}
else if (sel == 2)
{
static tConcreteFactory<FileLogger, CompleteWorker> fa;
return fa;
}
else if (sel == 3)
{
static tConcreteFactory<SocketLogger, ApproximateWorker> fa;
return fa;
}
else
{
static tConcreteFactory<SocketLogger, CompleteWorker> fa;
return fa;
}
}
// Client.cpp
Runner runner(fac);
//Factory.h
类tAbstractFactory
{
公众:
虚拟记录器*getLogger()=0;
虚拟工作者*getWorker()=0;
};
模板
类tConcreteFactory:公共选项卡StractFactory
{
公众:
loggerClass*getLogger(){返回新的loggerClass;}
workerClass*getWorker(){返回新的workerClass;}
};
//跑步者
班长
{
公众:
转轮(tAbstractFactory和fa)
{
m_logger=fa.getLogger();
m_worker=fa.getWorker();
}
私人:
记录器*m_记录器;
工人*m_工人;
};
//Factory.cpp
tAbstractFactory和getFactory(内部选择)
{
如果(sel==1)
{
静态混凝土;
返回fa;
}
否则如果(sel==2)
{
静态混凝土;
返回fa;
}
否则如果(sel==3)
{
静态混凝土;
返回fa;
}
其他的
{
静态混凝土;
返回fa;
}
}
//Client.cpp
跑步者(fac);
编辑:
我至少可以看到两个好处:
Runner
仅编程为出厂界面。依赖于运行程序
界面的客户端不会受到记录器
、工作者
等更改的影响就个人而言,对于一个小的代码库,不使用这种模式是完全可以的。在类/文件之间存在大量依赖关系的大型项目中,这将对编译时间和可伸缩性产生影响 共享或唯一指针可能会有所帮助,但您仍然可以将对对象的引用作为依赖注入变量 您确实需要确保在运行程序之前不会销毁对象(记录器、工作者)。依赖注入需要工厂。在本例中,我使用一个惟一的_ptr不是为了传递所有权,而是作为抽象类型的RAII安全句柄
#include <iostream>
#include <memory>
#include <exception>
struct Logger{
virtual void log() =0;
};
struct Logger1 : Logger {
void log() override { std::cout << " l1 " << std::endl;}
};
struct Logger2 : Logger {
void log() override { std::cout << " l2 " << std::endl;}
};
struct Logger3 : Logger {
void log() override { std::cout << " l3 " << std::endl;}
};
struct Worker{
virtual void work() =0;
};
struct Worker1 : Worker{
void work() override { std::cout << " w1 " << std::endl;}
};
struct Worker2 : Worker{
void work() override { std::cout << " w2 " << std::endl;}
};
struct Worker3 : Worker{
void work() override { std::cout << " w3 " << std::endl;}
};
struct Runner{
Runner(Worker& worker, Logger& logger): worker(worker),logger(logger) {};
Worker& worker;
Logger& logger;
void run(){
worker.work();
logger.log();
}
};
std::unique_ptr<Worker> mkUniqueWorker(int i){
switch (i) {
case 1: return std::make_unique<Worker1>() ;
case 2: return std::make_unique<Worker2>() ;
case 3: return std::make_unique<Worker3>() ;
case 4: throw std::runtime_error("unknown worker");
}
};
std::unique_ptr<Logger> mkUniqueLogger(int i){
switch (i) {
case 1: return std::make_unique<Logger1>() ;
case 2: return std::make_unique<Logger2>() ;
case 3: return std::make_unique<Logger3>() ;
case 4: throw std::runtime_error("unknown logger");
}
};
int main() {
auto worker = mkUniqueWorker(2);
auto logger = mkUniqueLogger(3);
Runner runner = Runner(*worker, *logger);
runner.run();
return 0;
}
#包括
#包括
#包括
结构记录器{
虚空日志()=0;
};
结构记录器1:记录器{
void log()重写{std::难道你不能根据堆栈来设计你的程序吗?如果不需要动态分配,建议使用堆栈。你不想在循环中使用new/delete,这很昂贵,特别是对于嵌入式系统,但我在你的示例中看到没有问题。谢谢你的回答。是的,性能不是一个问题,jus我看到很多人说使用堆栈分配的对象通常更好,因为它使对象的生存期非常明显,对于这样的情况,所有对象的生存期应该在主函数退出时结束,这似乎是个好主意。“没有一条普遍的规则,一条优先于另一条。”,如果你坚持比亚恩·斯特劳斯特鲁普的RAII原则(资源获取就是初始化)然后,相对于普通的new
和delete
(即堆),存在堆栈或资源容器中的明确偏好,例如std::vector
和std::shared_ptr
,它们可能使用堆来存储其内容。同意,该规则不是通用的,多态性调用指针或引用(但不一定是堆上的(原始)对象)。首先,RAII与堆栈无关。相反,大部分RAII对象封装了在堆上分配的对象。感谢您的响应。如果不清楚,第二个代码片段打算通过引用传递,只是使用对堆栈对象的引用。因此,我相信多态性应该有效,但前提是ual stack对象的类型是派生类。我想我不明白工厂在这种情况下为什么有用?在您的示例中,我似乎仍然在为派生类的每个可能组合复制代码。我可能最终会使用un