\$\begingroup\$
\$\endgroup\$
2
I was wondering if it would be possible to create a C# like syntax for dependency injection. My device is running constantly and acting more or less like a service. I came up with the following. My question, is it a good idea to use this, or something along the lines of this?
#include <iostream>
#include <memory>
#include <unordered_map>
#include <functional>
#include <vector>
class ServiceContext {
private:
// Mapping of interface type names to shared pointers of concrete instances
std::unordered_map<std::string, std::shared_ptr<void>> services;
public:
// Add a service to the context with dependency injection
template <typename Interface, typename Concrete>
void addSingleton() {
// Ensure that the Concrete class implements the specified interface
static_assert(std::is_base_of<Interface, Concrete>::value,
"Concrete class must implement the specified interface");
// Generate a key using the type name of the specified interface
const std::string key = typeid(Interface).name();
// Check if a singleton with the specified interface already exists
if (services.find(key) != services.end()) {
throw std::runtime_error("Singleton with interface already exists in ServiceContext");
}
// Create an instance of the Concrete class and store it in the services map
auto concreteInstance = std::make_shared<Concrete>(*this);
services[key] = std::static_pointer_cast<void>(concreteInstance);
}
// Get a service from the context
template <typename Interface>
std::shared_ptr<Interface> getService() const {
// Find the service in the map using the type name of the specified interface
auto it = services.find(typeid(Interface).name());
if (it != services.end()) {
// If found, return the service casted to the specified interface type
return std::static_pointer_cast<Interface>(it->second);
}
// If not found, return nullptr
return nullptr;
}
};
class Builder {
public:
ServiceContext Services;
// Build the application
template <typename T>
std::shared_ptr<T> build() const {
return std::make_shared<T>(Services);
}
};
class ILogger {
public:
virtual void PrintLine(const std::string& message) = 0;
virtual ~ILogger() = default;
};
class ActualLogger : public ILogger {
public:
ActualLogger(const ServiceContext& services)
{
}
void PrintLine(const std::string& message) override {
std::cout << "[Logger] " << message << std::endl;
}
};
class IButtons {
public:
virtual void Click() = 0; //This is only so I can simulate a button click
virtual void AddOnClickCallback(std::function<void()> callback) = 0;
virtual ~IButtons() = default;
};
class ActualButtons : public IButtons {
std::vector<std::function<void()>> onClickCallbacks;
public:
ActualButtons(const ServiceContext& services)
{
}
//This is only so I can simulate a button click
void Click() override {
std::cout << "Button clicked!" << std::endl;
// Notify all subscribers
for (const auto& callback : onClickCallbacks) {
callback();
}
}
void AddOnClickCallback(std::function<void()> callback) override {
onClickCallbacks.push_back(callback);
}
};
class IDisplay {
public:
virtual void Print(const std::string& message) = 0;
virtual ~IDisplay() = default;
};
class ActualDisplay : public IDisplay {
std::shared_ptr<ILogger> logger;
public:
ActualDisplay(const ServiceContext& services)
: logger(services.getService<ILogger>())
{
}
void Print(const std::string& message) override {
logger->PrintLine(message);
}
};
class IGUI {
public:
virtual void Run() = 0;
virtual ~IGUI() = default;
};
class ActualGUI : public IGUI {
std::shared_ptr<IDisplay> display;
std::shared_ptr<IButtons> buttons;
std::shared_ptr<ILogger> logger;
public:
ActualGUI(const ServiceContext& services)
: display(services.getService<IDisplay>())
, buttons(services.getService<IButtons>())
, logger(services.getService<ILogger>()) {
}
void Run() override {
logger->PrintLine("GUI is running...");
buttons->Click(); // Simulate a button click
display->Print("Hello from the GUI");
}
};
class MainApp {
std::shared_ptr<IGUI> gui;
public:
MainApp(const ServiceContext& services)
: gui(services.getService<IGUI>()) {
}
void Run() {
gui->Run();
}
};
int main() {
// Example usage
Builder builder;
builder.Services.addSingleton<IDisplay, ActualDisplay>();
builder.Services.addSingleton<ILogger, ActualLogger>();
builder.Services.addSingleton<IButtons, ActualButtons>(); // Register the buttons
builder.Services.addSingleton<IGUI, ActualGUI>();
auto app = builder.build<MainApp>();
if (app) {
app->Run();
}
return 0;
}
```
lang-cpp
std::any
?std::any_cast
performstypeid
check, which could be used in your container, although that would limit it to one object of type per type. \$\endgroup\$