Reconstructing ObjectBuilder
Ok, so I'm not so imaginitive with titles.
I'd going to be posting snippets of Unity internals here, and in particular getting into the sometimes ugly details of ObjectBuilder and how we use it, and how we've changed it.
To start off, I'd like to share probably the biggest change to the original OB API. For those who read my previous series, you'll remember the definition of IBuilderStrategy looked like this:
1 public interface IBuilderStrategy
2 {
3 object BuildUp(IBuilderContext context, object buildKey, object existing);
4
5 object TearDown(IBuilderContext context, object item);
7 }
To implement a strategy, you implemented this interface. When you were done, you returned the value you were building up. To invoke the rest of the strategy change, you wrote:
base.BuildUp(context, buildKey, existing);
This call would invoke the rest of the strategy chain and return the result back to you. Many strategies simply returned this value after having done their work.
This approach has the advantage of simplicity, but in practice is a real pain. The major issue is debugging and profiling. If you've ever seen a stack trace from when ObjectBuilder goes wrong, you'll know what I'm talking about. You can easily get 35 or 40 stack frames of nothing but BuilderStrategy.BuildUp, and it becomes next to impossible to figure out where the chain went wrong.
I got motivated to do something about this when we were doing some profiling on the Web Client Software Factory library, which uses OB1. Our perf guy told us "your problem is in ObjectBuilder." I asked him how he knew that, and he showed me his profile, which had nothing but OB calls in it, in gigantic stacks 70 or 80 layers deep. It is actually turning out that OB is not the determining factor, but we could see that becuase of all the noise.
So, as a result, the IBuilderStrategy interface now looks like this:
1 public interface IBuilderStrategy
2 {
3 void PreBuildUp(IBuilderContext context);
4
5 void PostBuildUp(IBuilderContext context);
6
7 void PreTearDown(IBuilderContext context);
8
9 void PostTearDown(IBuilderContext context);
10 }
I've explicitly split the processing of a strategy into two parts, Pre and Post. This is similar to the way WCF behaviors are designed. All the Pre- methods of the strategies are called going forward down the strategy chain, then the Post- methods are called in the reverse order.
Notice that the existing and buildKey parameters are now gone. These values are now included in the IBuilderContext, and strategies can change them as the strategy chain executes.
Now, the pre and post methods do not need to explicitly invoke the rest of the chain. Instead, the StrategyChain class now has ExecuteBuildUp and ExecuteTearDown methods that call the strategies in the appropriate order. Also, if you write a strategy that needs to short-circuit the chain and return early, there's a BuildComplete flag in the context that, if set true by a strategy, will stop the chain from continuing.
The net result of this chain is that stacks are a LOT shallower; you will only get as deep as the number of recursive dependencies. We also got a single spot to wrap and handle exceptions, which was a nice benefit. It turns out to be slightly faster as well, but perf wasn't a real priority behind this change.
There are a couple of lost capabilities, unfortunately. You can't completely change the context, recursively invoke the rest of the chain, and then switch back to the original context and execute the rest of the chain again. This was a sufficiently rare scenario (none of the built-in OB strategies used this technique) that I don't feel bad about losing it.
If you've used classic OB and are wondering what's going on with the strategies, I hope this helps.
Currently rated 4.0 by 1 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5