Hello! This is my first article in Habré. Faced today in the tape on the
description of Java-frameworks Spring and Tapestry, decided to browse Habra and find lovers of "competing" frameworks from Microsoft - in particular Composite UI Application Block (CAB) and Unity. To my surprise, I found nothing. Seeing the comments in the article about the Java framework, the request to describe the mechanism of dependency injection, I decided to start my cycle of articles about the .Net framework specifically by clarifying IoC issues. So, meet - the cornerstone (in the past) of the wonderful CAB engine - Microsoft Object Builder.
What it is and where it is used
Object Builder (hereinafter OB) is a mechanism for implicitly automatic composition of a set of dependent objects into a connected graph. OB was provided by Microsoft in May 2005 through its open-source
Patterns and Practices complex “open-source project” forge in tandem with the desktop application framework
CAB (or SmartClient). A set of fundamental classes CAB uses OB as a mechanism for injection of dependencies when a user creates engine objects (user controls, services, extensions, etc.).
How to set yourself
Download
CAB from here and put. The installer will add three assemblies to the GAC, one of which is an OB.
What does the Object Builder consist of and how does it work?
Essentially, an OB is an aggregation of several programming patterns. Briefly describe the structure.
')
- Builder - is a facade, the entry point to the mechanism of the object builder. Provides the user with build methods (
BuildUp
), a destruction method ( TearDown
). Builder contains a chain of strategies ( Strategy ), each of which is an atomic part of the builder. In addition, Builder has the ability to call the process of building an object to the desired user method - through the consumption of so-called. policy build. When a user wants to build an object, he calls BuildUp
and passes the type of object to be created to the method. At the exit, it receives an instance of this type. - Strategy - a part of which consists OB. Strategy implements some way to build an object. During its work, Builder follows the chain and applies each strategy from it to the object being created, thereby realizing the connections between the dependent objects. The user can create his own strategy and customize the process of creating objects.
- Policy - the policy of building objects. Each strategy may have some sort of policy. The policy provides the strategy context (i.e., the object environment) at the time the objects are created. A policy can serve to link strategies with each other. Quite a complicated thing, but, from the height of past years, I can say that Microsoft has made the OB functionality rich enough and (usually) the creation of new build policies is not required - just enough.
- Locator and LifeTimeContainer - in order to know where to get already built dependency objects, an OB requires a container of already created objects. Locator is a special class that can find an object in LifeTimeContainer and present it to the Builder to insert a dependency through a WeakReference . Accordingly, LifeTimeContainer manages the life cycle of objects (that is, it knows when objects should be deleted from memory and when they should not).
- Stage - in OB there is a life cycle of building an object. These are a few steps:
- Before creating (PreCreation)
- Creation
- Initialization
- PostInitialization
Each OB strategy works at some stage.
Soul requires examples! For example, let's create an object - the
IProgramService
microservice:
interface IProgramService
{
void DoWork();
}
* This source code was highlighted with Source Code Highlighter .
But its implementation:
class ProgramService : IProgramService
{
#region IProgramService Members
public void DoWork()
{
Console .WriteLine( string .Format( "[{0}] Program Service: DoWork()" , _guid));
}
#endregion
private Guid _guid = Guid .NewGuid();
public Guid Guid
{
get { return _guid; }
}
}
* This source code was highlighted with Source Code Highlighter .
Then the creation code will be like this:
Builder builder = new Builder();
Locator locator = new Locator();
IProgramService service = builder.BuildUp<ProgramService>(locator, null , null );
service.DoWork();
* This source code was highlighted with Source Code Highlighter .
Just like that.
Let us consider the strategies for inserting dependencies.
Application
If you look at the build code for the builder, you can see how he adds vital strategies to himself:
public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
Policies.SetDefault<ICreationPolicy>( new DefaultCreationPolicy());
if (configurator != null )
configurator.ApplyConfiguration( this );
}
* This source code was highlighted with Source Code Highlighter .
We will understand what methods you can create links between dependencies and how these strategies work.
TypeMappingStrategy (PreCreation) - Type matching policy. For example, in the last example, we explicitly indicated which type to create. But what if we don’t want to specify each time which class implements the
IProgramService
interface? What if we want to create an
IProgramService
, and who will be a specific type, we don’t want to know - let the programmer Vasya have a headache about it? We use the
ITypeMappingPolicy
creation
ITypeMappingPolicy
. This policy will indicate to the locator the correspondence between the interface and the specific type:
class TypeMappingPolicy : ITypeMappingPolicy
{
#region ITypeMappingPolicy Members
public DependencyResolutionLocatorKey Map(DependencyResolutionLocatorKey incomingTypeIDPair)
{
return new DependencyResolutionLocatorKey( typeof (ProgramService), null );
}
#endregion
}
* This source code was highlighted with Source Code Highlighter .
Registration policy occurs in this way:
builder.Policies.Set<ITypeMappingPolicy>( new TypeMappingPolicy(), typeof (IProgramService), null );
IProgramService service = builder.BuildUp<IProgramService>(locator, null , null );
* This source code was highlighted with Source Code Highlighter .
SingletonStrategy (PreCreation) - The strategy of creating an object as a singleton. This means that the created object for the specified type will be created only once. All subsequent attempts to create this type will be replaced by the substitution of an existing object. To do this, you must explicitly mark the created type with a special key, the so-called.
DependencyResolutionLocatorKey
:
IProgramService service = builder.BuildUp<ProgramService>(locator, null , null );
DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey( typeof (ProgramService), null );
locator.Add(key, service);
IProgramService service2 = builder.BuildUp<ProgramService>(locator, null , null );
* This source code was highlighted with Source Code Highlighter .
In this case,
service
and
service2
point to the same object.
ConstructorReflectionStrategy (PreCreation) is a strategy for creating an object through a constructor. The name speaks for itself - the strategy will select an object constructor marked with the
[InjectionConstructor]
attribute and for each parameter marked with the
[Dependency]
attribute it will find it through the locator. After which it will call the constructor, passing there the otrezolvlennye parameters.
[InjectionConstructor]
public ServiceConsumer([Dependency]IProgramService service)
{
_service = service;
}
* This source code was highlighted with Source Code Highlighter .
There are several ways to insert parameters. Through the properties of the attribute Dependency, you can specify the type to be created, as well as define the behavior if the type you are looking for is not found. For example, create a new (
CreateNew
), or throw an exception:
[InjectionConstructor]
public ServiceConsumer(
[Dependency(CreateType= typeof (ProgramService), NotPresentBehavior=NotPresentBehavior.Throw)]IProgramService service)
{
_service = service;
}
* This source code was highlighted with Source Code Highlighter .
It goes without saying that if we create one object that has a dependency on another, then another object must be resolved in the locator:
IProgramService service = builder.BuildUp<ProgramService>(locator, null , null );
DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey( typeof (IProgramService), null );
locator.Add(key, service);
ServiceConsumer consumer = builder.BuildUp<ServiceConsumer>(locator, null , null );
* This source code was highlighted with Source Code Highlighter .
CreationStrategy (reation) is the usual creation strategy. In the very first example above, the object is built just through the creation strategy. How it works - first, the strategy looks for a custom
ICreationPolicy
policy, and if it does not find the policy, it uses the “native” one. The native creation policy instructs the builder to determine the first object constructor (through reflection) and run the build chain on each dependency (parameter) in the constructor. Then execute it through the
Activator
. A feature of this strategy is the fact that you can transfer an already created object to it. Then the strategy will do nothing :) Just transfer control to other strategies that have a
Stage
following
Creation
. Once again the last, but in other words - we can create an object through
new()
, pass it to the builder. In this case, the builder will not create an object, but simply insert dependencies into it.
PropertyReflectionStrategy +
PropertySetterStrategy (Initialization). Strategies (in fact, semantically, this is one strategy), allowing to insert dependencies into the properties of an object. In a nutshell, this is how
PropertyReflectionStrategy defines the properties of an object, marked with the
[Dependency]
attribute, recognizes the types of objects that need to be inserted, creates an
IPropertySetterPolicy
policy, where it lists this information. Then, walking along the chain,
PropertySetterStrategy starts to work, which finds the policy, from it determines the properties that need to insert objects, resolves the objects and sets them in the right places. The strategy separation chip is here for the user to use his
IPropertySetterPolicy
.
[Dependency(CreateType= typeof (ProgramService),
NotPresentBehavior=NotPresentBehavior.CreateNew)]
public IProgramService ProgramService
{
set { _service = value ; }
get { return _service; }
}
* This source code was highlighted with Source Code Highlighter .
This code can be rewritten easier:
[CreateNew]
public ProgramService ProgramService2
{
set { _service = value ; }
}
* This source code was highlighted with Source Code Highlighter .
MethodReflectionStrategy +
MethodExecutionStrategy (Initialization). In essence, these two strategies are isomorphic to the strategies of
Property
dependencies. Only, instead of setting the property, a call is made to the method marked with the
[InjectionMethod]
attribute.
[InjectionMethod]
public void DoWork([Dependency]IProgramService service)
{
service.DoWork();
}
* This source code was highlighted with Source Code Highlighter .
BuilderAwareStrategy (PostInitialization). In the OB there is such a special interface
IBuilderAware , in which there are two methods -
OnBuiltUp
,
OnTearingDown
. You can implement them in your class, then when you build (or destroy) an object, the strategy will cause them. Very useful if you need to run something immediately after creating the object (when all dependencies are in place).
#region IBuilderAware Members
public void OnBuiltUp( string id)
{
Console .WriteLine( "Built Up!" );
}
public void OnTearingDown()
{
Console .WriteLine( "Tearing down!" );
}
#endregion
* This source code was highlighted with Source Code Highlighter .
I listed all the “native” OB strategies. In the engines, developers add their strategies so that you can link the specialized aspects of the framework. Unfortunately, completely disassemble all aspects of an OB — for example, the named instances are not affected, the politician theme is not fully developed, and so on. The reader, I think, if he wants, will figure it out himself :)
How to use it in real applications, pros and cons
The answer is simple - as an infrastructure aspect. If you want to implement in your application architecture that involves injection of dependencies, then the OB is what the doctor prescribed. OB itself was not originally intended for direct use. In the CAB engine, its methods turn into special collections - OB facades, which greatly facilitates the work of the programmer.
Minuses: The most, perhaps, the big minus of the solution - OB is heavy. It clutters up code with attributes (you can't do without them!), And its method
BuildUp
, you know, is not one of the fastest. This problem can be solved by optimizing links in the class graph — in fact, minimizing the number of dependencies with maximum code isolation.
Pros: Low connectivity code, objects, and all the ensuing. This is the simplicity of unit tests, and autonomous (when you don’t have to put anything up with pens) microservice architecture and the like.
Future Object Builder and Conclusion.
Now OB is outdated :( Yes, sorry, I wrote an article on outdated technology, but it seems to me that it is useful to start from the basics. In addition, it will be difficult to talk about Microsoft Object Builder 2 without understanding what Microsoft Object Builder was Microsoft replaces the CAB engine with a more modern Unity application. The Unity Application Block is an even more flexible platform with the long-awaited support for WPF. Actually, in the following articles I’ll tell you what CAB is and gradually move to Unity. Here are the links:
CAB:
msdn.microsoft.com/en-us/library/aa480450.aspxCAB:
www.codeplex.com/smartclientUnity:
msdn.microsoft.com/en-us/library/cc468366.aspxUnity:
www.codeplex.com/unityDifference of CAB from Unity:
www.codeplex.com/CompositeWPF/Release/ProjectReleases.aspx?ReleaseId=16941Thanks for attention.