Skip to content

Instantly share code, notes, and snippets.

@bryanedds
Last active April 19, 2023 11:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bryanedds/16340bcad40a743f93f3 to your computer and use it in GitHub Desktop.
Save bryanedds/16340bcad40a743f93f3 to your computer and use it in GitHub Desktop.
Abstract Data Types and Dependency Injection in C++
#include "functional"
#include "iostream"
#include "string"
namespace proj
{
/// For background on the discussed subject matter, first see this gist article on Abstract Data
/// Types and Alternative Forms of Polymorphism in C++ - https://gist.github.com/bryanedds/6a8ea281bb4a3d8b559e
/// Dependency injection in C++ is easy, if you do it carefully. The quickest way to do it
/// wrong is to use a Singleton. A slower way to do it wrong is to use objects (that will
/// often just suck you into the OOP quagmire). Instead you should use an interface implemented
/// with an Adt. Interfaces destined for use with Adts are a little different. Like Adts, they
/// have no public instance functions, but rather public static functions instead.
class IMessenger
{
protected:
virtual std::string encrypt(const std::string& message) const = 0;
virtual void send(const std::string& message) = 0;
public:
static void send_message(IMessenger& messenger, std::string& message)
{
auto encrypted = messenger.encrypt(message);
messenger.send(encrypted);
}
};
/// Like Adts, all public static functions are forwarded to the interface's enclosing namespace
/// to enable ad-hoc polymorphism and better genericity.
void send_message(IMessenger& messenger, std::string& message)
{
IMessenger::send_message(messenger, message);
}
/// Here we implement the above interface with an Adt whose implementation here is mostly
/// faked. We don't need to write any forwarding functions since the forwarding function
/// written for the above interface already took care of it :)
class Messenger : public IMessenger
{
private:
const int connectionHandle;
protected:
std::string encrypt(const std::string& message) const override
{
// here we pretend we are encrypting a message fit for a specific type of connection
return std::string(message.crbegin(), message.crend());
}
void send(const std::string& message) override
{
// here we pretend to send the message over a connection with the given handle
return;
}
public:
Messenger()
: connectionHandle(0) // here we pretend we get a real connection handle
{ }
};
/// Finally, like in the previous gist article, we implement an example Adt, except this time
/// we call it something more specific so as not to confuse it with the Adt above.
class World
{
private:
IMessenger& messenger;
public:
World(IMessenger& messenger) :
messenger(messenger) // dependency... injected!
{ }
static int func(const World& world)
{
send_message(world.messenger, std::string("We're returning 25!"));
return 25;
}
};
/// Again, we're forwarding our Adt's static functions for ad-hoc polymorphism and genericity :)
int func(const World& world)
{
return World::func(world) * 5;
}
}
int main(int argc, const char* argv)
{
// I sometimes like to open namespaces in local function scopes
using namespace std;
using namespace proj;
// here we simply create our dependency on the stack
auto messenger = Messenger();
// and then we create the world on the stack as well, injecting its needed dependencies
auto world = World(messenger);
// software architecture can be surprisingly elegant and easy in C++ - if we set ourselves
// up properly :)
cout << func(world);
// great success!
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment