您的位置:首页 > 编程语言 > C语言/C++

C++ IOC

2016-04-12 09:52 447 查看
Fruit is a
dependency injection framework for C++, loosely inspired by the Guice framework for Java. It uses C++ metaprogramming together with some new C++11 features to detect most injection problems at
compile-time. It allows to split the implementation code in "components" (aka modules) that can be assembled to form other components. From a component with no requirements it's then possible to create an injector, that provides an instance of the interfaces
exposed by the component.

See the
Fruit website for more information, including installation instructions, tutorials and reference documentation.

////////////////////////////////


Dependency Injection in C++

Posted on July
30, 2008

One goal of object oriented programming is to encapsulate functionality 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 written 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 mechanisms in Java to create instances of classes based on demand criteria.

Updated: links and using the code formatter. Changed Class from Single Letter to full names to be clearer and to not conflict with HTML tags.

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 mechanism
of Java,  this approach cannot be ported to C++.  However, 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 mechanism 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  class template  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 Stand which
stands alone.  Additionally, we define class Dep which depends on class Stand.

In C++ Objects can be allocated from three main memory areas:   the data segment, the stack, and the heap.  When the life span of an object must extend beyond the scope of the current stack frame, the object cannot be allocated on the stack.  To allocate on
the heap, the programmer  writes something like:

1
Stand*
stand = 
new
 
Stand;
2
Dep*
dep = 
new
 
Dep(stand);
This is a method of object creation: it is not possible to use the ‘new’ operator to share an instance of ‘stand’ that already exists.  Thus we are linking the creation and usage of the classes together.  If the programmer wishes to use a globally scoped instance
of the object, he can do so using the fully qualified name of the instance.  More complicated approaches include creating an intermediate object that acts as a monitor for a pool of available objects.

The parameter ‘stand’ to cxonstructor Dep() above allows instances of Dep to work with instances of Stand regardless of the creation method.  Writing the classes  like  ‘Stand’ and ‘Dep’ require something external to wire them up. This is intentional, as the
method of creation of ‘stand’ is a policy decision that is outside the scope of ‘Dep’.  If the code is written exactly like 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  class template 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 application is initialized like this:

1
Supply<
Dep > 
register
(createDep,destroyDep);
Where createDep and destroyDep are function pointers.

We can be succinct and omit the functions for create and destroy, in which case the supply is smart enough to just call the constructor and destructor. In another file we can register how to create and destroy L

1
Supply<
Stand > 
register
();
In order to fetch an instance of type Stand we call:

1
Stand
* stand = Supply< Stand >.fetch();
This code can exist inside the createD function which would look like this:

1
Dep*
createDep(){
2
 
3
Stand
* stand = Supply< Stand >.fetch();
4
 
5
return
 
new
 
Dep(stand);
6
 
7
}
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 subdivision 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 on-line 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:

1
Supply
< DatabasePool, DBPOOL_OLTP > 
register
(createOLTPPool,
freeOLTPPool);
2
 
3
Supply<
DatabasePool, DBPOOL_CONTENT >
register
(createContentPool,
freeContentPool);
4
Supply<
DatabasePool, DBPOOL_LEGACY >
register
(createLegacyPool,
freeLegacyPool);
Then when a class requires a DatabasePool to perform it’s work, it would register to resolve the  dependency  with:

1
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

hand-held 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 years.  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 application types beyond 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 messaging 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
uniquely 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 responsibility.  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 receives 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.

https://github.com/admiyo/CppInject

I had originally meant to expand the scope of this discussion to talk about entities stored inside a database, but quickly realized that it was at least 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

url:http://adam.younglogic.com/2008/07/dependency-injection-in-c/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 设计原理 ioc