Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/145.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ c++;依赖注入多态性_C++_Memory Management_Dependency Injection - Fatal编程技术网

C++ c++;依赖注入多态性

C++ c++;依赖注入多态性,c++,memory-management,dependency-injection,C++,Memory Management,Dependency Injection,我有一个关于多态类依赖注入的最佳实践的问题。我是C++新手,请原谅我这是一个明显的问题。假设我有一个类运行程序,它需要接受两个对象,一个记录器和一个工作者。Logger是一个抽象类,有两个子类,比如FileLogger和SocketLogger。类似地,Worker是一个抽象类,有两个子类,比如ApproximateWorker和CompleteWorker Runner类将从main()创建,并将基于配置文件或类似文件创建记录器和工作程序。我在某处和其他地方读了很多书,一般的感觉似乎是更喜欢堆

我有一个关于多态类依赖注入的最佳实践的问题。我是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);
编辑:

我至少可以看到两个好处:

  • 添加新案例或更改具体记录器/工作者的类型时,Client.cpp不会受到影响。也就是说,您可以限制Factory.cpp内部的更改,以便客户端逻辑(实际使用创建的对象)保持不变

  • 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