Last active
April 19, 2023 11:52
-
-
Save bryanedds/16340bcad40a743f93f3 to your computer and use it in GitHub Desktop.
Abstract Data Types and Dependency Injection in C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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