1
\$\begingroup\$

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;
}
```
asked Jan 2, 2024 at 11:47
\$\endgroup\$
2
  • \$\begingroup\$ Have you considered std::any? std::any_cast performs typeid check, which could be used in your container, although that would limit it to one object of type per type. \$\endgroup\$ Commented Jan 2, 2024 at 13:55
  • \$\begingroup\$ Good suggestion. One object per type is a good thing, since you don't want multiple instances in this case. BTW, its already limited by checking the interface type. \$\endgroup\$ Commented Jan 2, 2024 at 14:38

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.