Interceptors
Sometimes, you need to add certain behavior to objects that activates once they have been created. Or perhaps you want to log object creation. One common example is an interceptor that runs a method on an object in a new thread. Let's look at the necessary code to implement that.
Custom logic
First, we want to start with an interface that marks the type as being runnable:
interface IThreadRunnable { void run(); }
And the code that will run one of these:
void run (IThreadRunnable runnable) { new Thread(&runnable.run).start(); }
Writing the interceptor
The interceptor class is simple. It must have a default constructor and a method void intercept(void[] object, TypeInfo? type):
class ThreadRunnableInterceptor { void intercept (void[] object, TypeInfo type) { // Any code you want here -- in this case, we run the object if it's runnable. if (implements(type, typeof(IThreadRunnable)) { // Forgive the ugly cast chain. run (cast(IThreadRunnable)*cast(Object*)&object.ptr); } } }
Why the funny signature?
A previous incarnation of dconstructor used templates for interceptors. This required the interceptors to be specified as template parameters, which was cumbersome and led to executable bloat and template bloat (some of my samples compiled to three or four megabyte executables and took a full minute to compile).
With tango.core.RuntimeTraits?, you can quickly check whether the item is an object, and if it is, cast it to Object, then whichever interface you need. This does as well as most languages under the circumstances, except that most languages support autoboxing to Object. Also, it contributes greatly to reducing template bloat.
Using the interceptor
All we have to do now is add that to our own Builder:
module mypackage.Builder; public import dconstructor.api; public Builder builder; static this() { builder = new Builder; builder.interceptors ~= builder.get!(ThreadRunnableInterceptor); }
Now just have your modules import mypackage.builder rather than dconstructor.default_builder, and it will work.
