One goal of object oriented programming is to encapsulate functionaliy within a class. If one class requires another in order to collaborate on a larger process, the programmer must deal with the wiring up of the two classes. Much has been writen about the correct way to do this in various object oriented languages. The term “Inversion of Control” and the related concept of “Dependency Injection” have become part of the common language of software development, at least in the Java world, due to projects like Pico/nano container, The Spring framework, and earlier efforts inside J2EE and ATG Dynamo. These frameworks make use of the introspection mechansims in Java to create instances of classes based on demand criteria.
One drawback of the introspection mechanism is that it works outside of the type system. A programmer can write code that compiles, but is semantically incorrect. The only way to test this code is by running it. Since C++ lacks the introspection mechansim of Java, this approach cannot be ported to C++. Hoever, C++ Has a much richer system for developing type based programs anyway: templates. Many of the techniques from Alexandrescu’s Modern C++ Design can be linked together to build an inversion of control mechanism that is type safe. An added benefit is that the mecahnism suffers from none of the introspection overhead of the Java approach, and thus can be used inside performance critical code.
The general approach is to register a construction and destruction function for a given class with a factory for that class. The factory is a templatized class that supports partial template specialization, allowing multiple definitions for a class. The factory functions are capable of calling other factory functions, to fetch and create dependencies for the class in question. For instance: Let’s define a class S which stands alone. Additionally, we define class D which depends on class S.
In C++ code we would traditionally write something like:
S* s = new S();
D* d = new D(s);
However, there may be an instance of ’s’ already created. Thus we are linking the creation and usage of the classes together.
There is much code out there classes like ‘D’ actually have the knowledge of how to create or fetch ’s’. The classes ‘S’ and ‘D’ at least start by requiring something external to wire them up. This is intentional, as the method of creation of ’s’ is a policy decision that is outside the scope of ‘D’. In the example above, the policy is hard coded to be a ‘new’. IDeally, we would separate this policy from the usage of the classes.
I am now going to introduce a templatized class called Supply. A supply is a class that maintains references to factories and instances produced by those factories. The policy of how to create and destroy these objects are registered with the Supplies when the appication is initialized like this:
Supply<D> register(createD,destroyD);
Where createD and destroyD are function pointers.
We can be tricky and omit the functions for create and destry, in which case the supply is smart enough to just call the constructor and destructors, but that doesn’t make for a very easy to understand example. In another file we can register how to create and destroy S
Supply<S> register();
In order to fetch and instance of type S we call:
S* s = Supply<S>.fetch();
This code can exist inside the createD function which would look like this:
D* createD(){
S* s = Supplys<S>.fetch();
return new D(s);
}
So far we have divided the construction logic from the usage by adding a single function to create the object graph on demand. The fetch function can be made smart enough to check if it has an instance available and, if so, to return that instance instead of calling the create function.
The level of Inversion of Control described thus far is fairly paltry, and would not be a valuable addition to most programs. It lacks at least two things to be truly useful. FIrst, there needs to be a way to distinguish between two different instances of the same class. Even more important is the ability to scope those instances to a particular subdivsion of the application. There are two techniques we can use to implement these features: either by calling a different function for each instance, or passing in a parameter that is used to distinguish between the instances. The key tool for selecting between two functions is partial template specialization. Specifically, we want to use a primitive value to distinguish between different creation policies. For instance, suppose an application requires several different database schemas. One pool is for online transaction processing (OLTP), one is for content management, and one is for integration with a legacy system. A class named DatabasePool controls connections to each database. We would need to register each DatabasePool separately:
Supply<DatabasePool, DBPOOL_OLTP> register(createOLTPPool, freeOLTPPool);
Supply<DatabasePool, DBPOOL_CONTENT> register(createContentPool, freeContentPool);
Supply<DatabasePool, DBPOOL_LEGACY> register(createLegacyPool, freeLegacyPool);
Then when an class requires a DatabasePool to perform it’s work, it would register to resolve the dependency with:
Supply<DatabasePool, DBPOOL_LEGACY>.fetch()
To scope an instance of a class to a specific portion of the application requires understand the application type. The types of applications written in C++ range from small to large. Here is an incomplete list:
- Embedded programming
- handheld wireless device applications
- Desktop GUI applications
- System tray applets
- HTTP based applications
- streaming media network services
- TELNET style connection based network services
- Asynchronous messaging for systems integration
- Data intensive super computing (DISC)
- Massively parallel message passing interface (MPI) Supercomputing tasks.
The scopes for each of type of application is going to be somewhat different. In embedded programming for really small devices we can probably make most objects global. Something as complex as an dependency injection may be more than the device can handle. Working up the stack, we come across applications that require threading, and that may be handling multiple users at the same time. This adds a new requirement: our dependency injection mechanism must be thread safe. Here we can lean upon the lessons learned by many people that have been building enterprise applications in Java for many yeakmrs. In the J2EE stack, in ATG Dynamo and now in the Spring framework, objects can be scoped globally, per application (assuming multiple apps run in the same container), per session, and per http request. This is a pretty good breakdown of scopes, and applies to many of the application types besides HTTP. For instance, a TELNET server would have one session per socket connection , and divide up the stream into smaller sequences that map to requests. An asynchronous messagin protocol would not have sessions, as state is contained completely inside the messages, but certain patterns like scatter/gather might have scopes that span multiple messages. Applications at the smaller and larger ends of the spectrum have a similarity in that the tasks tend to be the only thing running on the machine, and thus there is less of a need to distinguish between session, application and global scoping.
I’m going to describe the HTTP model with the hope that applying it to other types of applications will be done fairly similarly. THe scopes from shortest to longest are : request, session, application, and global. When the network server starts running, the first global objects get initiated. These in turn activate the applications. One of these objects will listen for network connections and accept incoming requests. The first such request will force the creation of a session. Once the request is handled and the response is sent, the request object is freed along with all of its corresponding objects, while the session and associated objects remain in the application, usually on a timer that will free them after some period of no activity. The session is uniquly identified with a cookie sent with the response, and sent back to the server with each subsequent request. The Request object will then associate itself with that session. Once the hierarchy is established from request up to session, all object resolution requests are fulfilled via a chain of responsbility. The application specific code should only resolve via Supply<T>.fetch(Request). If the object requested is not registered at the request level, the call is forwarded on to Supply<T>.fetch(Session), and then on to Supply<T>.fetch(Application), and finally to Supply<T>.fetch() which resolves globally. Failure to resolve globally will trigger an exception.
The onus is upon the application developer to correctly register the scope of requested objects. Each object will then be created upon first demand, and reused until the lifetime had been completed. For example a DHCP server that recieves a packet containing a ClientMACAddress will parse the field from the packet when first accessed, and then subsequently will use the parsed object until the DHCP request has been sent, at which time the ClientMACAddress will be freed.
Here is a sample implementation.
http://adam.younglogic.com/resolver/
I had origianally meant to expand the scope of this discussion to talk about entities stored inside a database, but quickly realized that it was at leas as complex as the discussion above. If the Entire database can be held in memory at once, you can make them global objects. Anything more than that requires a complicated caching scheme. That will be a separate article