-
Notifications
You must be signed in to change notification settings - Fork 81
Getting started
Yohan edited this page Jul 5, 2017
·
13 revisions
Nope, I'm good, show me some advanced features.
Say we have an application that needs to serialize and write some messages.
// We can provide an abstraction for the serializer since we only
// need to know that we want to serialize a message in a stream.
class IMessageSerializer
{
public:
virtual ~IMessageSerializer() = default;
virtual void serialize(const std::string& message, std::ostream& stream) = 0;
};
// We implement a basic serializer which will write the length of
// the message before writing the message itself.
class LengthPrefixedMessageSerializer : public IMessageSerializer
{
public:
void serialize(const std::string& message, std::ostream& stream) override
{
stream << message.size();
stream << message;
}
};
// The abstraction on the writer tells us that we can pass this
// object a message and it will do the job of writing it somewhere.
class IMessageWriter
{
public:
virtual ~IMessageWriter() = default;
virtual void write(const std::string& message) = 0;
};
// Eventually, we implement a writer that will be responsible for serializing
// the messages in the console. You can see it is injected a serializer.
class ConsoleMessageWriter : public IMessageWriter
{
public:
explicit ConsoleMessageWriter(std::shared_ptr< IMessageSerializer > serializer)
: m_serializer(serializer)
{
}
void write(const std::string& message) override
{
m_serializer->serialize(message, std::cout);
}
private:
std::shared_ptr< IMessageSerializer > m_serializer;
};
Now, let's configure the container to let it know about these components.
Since the container is responsible for your components creation, lifetime, etc. we have to register the types the application will need very close to the beginning of the program and keep a pointer on the built container.
#include "Hypodermic/Hypodermic.h"
class Application
{
public:
Application()
{
// Components are registered in a ContainerBuilder
Hypodermic::ContainerBuilder builder;
// What we say here is: when I need an IMessageSerializer,
// I want you to use this implementation.
builder.registerType< LengthPrefixedMessageSerializer >()
.as< IMessageSerializer >();
builder.registerType< ConsoleMessageWriter >().as< IMessageWriter >();
// Actually build the `Container` we have just configured.
m_container = builder.build();
}
// We will implement this one in a second
void run();
private:
std::shared_ptr< Hypodermic::Container > m_container;
}
So how is this helping me? Well, we just have to ask the container to build an instance of IMessageWriter
.
void Application::run()
{
// Container, give us an instance of `IMessageWriter`.
auto messageWriter = m_container->resolve< IMessageWriter >();
// Alright then, we can write some message.
messageWriter->write("The app is running");
}
So what is happening when we resolve IMessageWriter
?
- The container is looking for a registration matching this type
- It sees that
IMessageWriter
is mapped toConsoleMessageWriter
- Oh but look,
ConsoleMessageWriter
needs aIMessageSerializer
- So it looks for
IMessageSerializer
and sees it is mapped toLengthPrefixedMessageSerializer
- It instantiates
LengthPrefixedMessageSerializer
and pass it to the constructor ofConsoleMessageWriter
- ... and eventually returns a fresh instance of
ConsoleMessageWriter
You no longer have to know in what order you should construct your objects.
Not enough for you? Discover what you can do by reading this section.