Introduction
This article assumes knowledge of the Service Locator pattern. The article uses terminology and source examples from Martin Fowler's Inversion of Control Containers and the Dependency Injection Pattern. Also see the CodeProject article A Basic Introduction on Service Locator Pattern.
Background
Typical examples are in C# and Java. This article provides a simple but complete Service Locator framework in C++.
The basic idea behind the Service Locator is to be able to register one or more classes with a repository and then ask the repository to create instances of those classes using an ID rather than the actual class name. By doing this, it makes the client code decoupled from the actual implementation of the class, allowing clients to register different implementations of the same interface and thus being able to swap different sets of functionality without changing the code that uses the class. This also helps with unit testing because references to a class that would ordinarily be hard-coded no longer needs to be.
And finally, this also reduces build dependencies.
Sneak peak at usage
In this article, Martin Fowler's example classes are used: MovieFinder
is an interface (abstract pure-virtual class) and ColonDelimitedMovieFinder
is a class which implements MovieFinder
.
The Service Locator code will allow us to register ColonDelimitedMovieFinder
with a string ID such as "finder", and then create instances of the ColonDelimitedMovieFinder
classes by just using the string name "finder", assigning the referencing to a MovieFinder
pointer.
Example registering the class, storing it under the string ID "finder":
locator.register_class<ColonDelimitedMovieFinder>( "finder" );
Example creating an instance of this class:
MovieFinder* finder = locator.get_single_instance<MovieFinder>( "finder" );
Using the Code
(You just need the single header, servicelocator.h. The namespace sl
is used.)
Derive your interfaces (pure-virtual abstract classes) from interface_t
. Example:
struct MovieFinder : sl::interface_t
{
virtual vector<Movie> findAll() = 0;
};
Derive your template classes from member_t
, passing in the class name as the template parameter. Example:
class ColonDelimitedMovieFinder : public sl::member_t<ColonDelimitedMovieFinder>
{ ...
Create an instance of servicelocator_t
. Example:
sl::servicelocator_t locator;
Register classes using the register_class
method. The class to be registered is the template parameter. The function parameter is the string ID representing the class. Example:
locator.register_class<ColonDelimitedMovieFinder>( "finder" );
Now, the string ID (such as "finder") above can be created with an instance of ColonDelimitedMovieFinder
, without referencing ColonDelimitedMovieFinder
by name. Assuming that ColonDelimitedMovieFinder
is redefined to implement the MovieFinder
interface like so:
class ColonDelimitedMovieFinder : public sl::member_t<ColonDelimitedMovieFinder>,
public MovieFinder
Then, there are two methods for creating an instance of ColonDelimitedMovieFinder
. One as a singleton (so that subsequent calls to create all return the same instance), and one as a transient instance, where a new instance is created with each call.
The call to create a singleton instance is get_single_instance
, and it looks like this:
MovieFinder* finder = locator.get_single_instance<MovieFinder>( "finder" );
(Again, note that the class ColonDelimitedMovieFinder
is not referenced, but that's what's created because the string ID "finder" was registered with ColonDelimitedMovieFinder
above.) Note that the object returned from get_single_instance
does not need to be freed. Only a single instance is created and is owned by the Service Locator. When the locator
object goes out of scope, or is freed, all singletons are freed.
The second way to create an instance is via get_new_instance
, and it looks like this:
auto_ptr<MovieFinder> finder = locator.get_new_instance<MovieFinder>( "finder" );
For convenience, get_new_instance
returns an auto_ptr
, which is safer to return than a raw pointer. However, if the client doesn't want an auto_ptr
object, it's simple enough to call release()
on the auto_ptr
and get the raw object, which then can be managed by the client as desired. But in that case, the client is responsible for calling delete
on the raw object eventually.