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;
}

```
\$\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\$ Jan 2 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\$ Jan 2 at 14:38

0

Your Answer

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

Browse other questions tagged or ask your own question.