假设我有这样一个程序:
文件main.cpp
#include "something.hpp"
int main(int argc, char* argv[]) {
some = new Something();
return 0;
}它将链接到由以下文件组成的.so库:
文件logger.hpp
#include <iostream>
class Logger {
public:
Logger();
void log(char);
void set_name(char);
private:
char m_name;
};文件logger.cpp
#include "logger.hpp"
Logger::Logger() {}
void Logger::log(char msg) {
std::cout << this->m_name << " : " << msg;
}
void Logger::set_name(char name) {
this->m_name = name;
}文件something.hpp
#include "logger.hpp"
class Something {
public:
Something();
};文件something.cpp
#include "something.hpp"
Something::Something() {
logger->log("hello !");
}现在的代码将在something.cpp中失败,因为logger从未被定义过。我可以通过添加logger = new Logger()来解决这个问题。但是我只想创建一个新的Logger实例,如果在使用这个库的程序/库中没有创建任何实例。当已经创建了一个实例时,我可以通过添加extern Logger logger;来使用它。但是,当没有创建实例时,这是行不通的。有什么建议吗?
注意:我已经使用了Gtkmm4 / Glibmm2.6,也许有一个使用Gtk或Glib的解决方案。
发布于 2021-01-22 13:30:36
第一种方法: Singleton
正如注释中所讨论的,您可以使用单例设计模式来解决这个问题。但是,请记住这个模式有几个缺点,其中两个是:
在编写高质量的软件时,这是真正的问题。另外,对于您的特殊情况,请确保阅读这个答案,这说明了如何确保所有内容都适当地链接,这样就不会有多个单例实例。
第二种方法:依赖注入
我决定在这里发布一个答案,以说明另一种解决上述两个问题的方法:依赖注入 (DI)。使用DI时,不需要创建依赖项,而是通过参数注入依赖项。例如,而不是:
Something::Something() {
auto logger = new Logger(); // Dependency creation (not injection)
logger->log("hello !");
}你会得到这样的东西:
Something::Something(Logger* p_logger) { // Logger dependency injected through p_logger
p_logger->log("hello !");
}请注意,DI本身并不能解决“一个实例”问题。必须注意创建依赖项一次(通常在main中),然后将它们作为参数传递给使用它们。但是,全局访问问题得到了解决。
您可以通过抽象您的依赖关系将其提升到另一个级别。例如,您可以为您的Logger类编写一个接口并使用它:
// Somewhere in your library:
class ILogger
{
public:
virtual ~ILogger() = default;
virtual void log(const std::string& p_message) = 0;
virtual void set_name(const std::string& p_name) = 0;
};
// In Logger.hpp:
class Logger : public ILogger {
public:
Logger();
void log(const std::string& p_message) override;
void set_name(const std::string& p_name) override;
private:
std::string m_name;
};
// In something.hpp/cpp:
Something::Something(ILogger* p_logger) { // Logger dependency injected through p_logger
p_logger->log("hello !");
}要做到这一点,您的main可能如下所示:
int main(int argc, char* argv[]) {
// Here, you create your logger dependency:
std::unique_ptr<ILogger> concreteLogger = std::make_unique<Logger>();
concreteLogger->set_name("frederic");
// Here, you inject it. From here on, you will inject it everywhere
// in your code. The using code will have no idea that under the hood,
// you really are using the Logger implementation:
some = new Something(concreteLogger.get());
// Note: if you use `new`, do not forget to use `delete` as well. Otherwise,
// check out std::unique_ptr, like above.
return 0;
}这样做的好处是,您现在可以在没有任何(除了main)关心的情况下,随时更改记录器的实现。您还可以创建记录器的模拟,以防您想要对Something进行单元测试。这比在单元测试中处理单例更加灵活,从术语上讲,这将产生各种问题(很难调查/解决)。换句话说,这解决了上面提到的第二个问题。
请注意,DI的一个可能的缺点是,最终可能会有很多参数,但在我看来,它仍然优于使用单例。
https://stackoverflow.com/questions/65832122
复制相似问题