9 November 2008

Lazy Loaded Interceptors

by mo


Patterns of Enterprise Application Architecture defines **Lazy Load** as:

An object that doesn’t contain all of the data you need but knows how to get it.

A while back I was trying to figure out how to lazy load objects from a container, so that I didn’t need to depend on the objects dependencies needing to be wired up in the correct order. The syntax I was looking for was something like the following.

container.AddProxyOf(
  new ReportPresenterTaskConfiguration(),
  new ReportPresenterTask(
    Lazy.Load<IReportDocumentBuilder>(),
    Lazy.Load<IApplicationSettings>()
  )
);

Lazy.Load<T> will return a proxy in place of an actual implementation. This is just a temporary place holder that will forward the calls to the actual implementation. It wont load an instance of the actual type until the first time a call is made to it.

public class when_calling_a_method_with_no_arguments_on_a_lazy_loaded_proxy : lazy_loaded_object_context
{
    [Observation]
    public void should_forward_the_original_call_to_the_target()
    {
      target.should_have_been_asked_to(t => t.OneMethod());
    }

    protected override void establish_context()
    {
      target = dependency<ITargetObject>();
      test_container
          .setup_result_for(t => t.find_an_implementation_of<ITargetObject>())
          .will_return(target)
          .Repeat.Once();
    }

    protected override void because_of()
    {
      var result = Lazy.Load<ITargetObject>();
      result.OneMethod();
    }

    private ITargetObject target;
}

When the method OneMethod is called on the proxy, it will forward the call to the target, which can be loaded from the container. The implementation depends on Castle DynamicProxy, and looks like the following.

public static class Lazy
{
  public static T Load<T>() where T : class
  {
    return create_proxy_for<T>(create_interceptor_for<T>());
  }

  private static LazyLoadedInterceptor<T> create_interceptor_for<T>() where T : class
  {
    Func<T> get_the_implementation = resolve.dependency_for<T>;
    return new LazyLoadedInterceptor<T>(get_the_implementation.memorize());
  }

  private static T create_proxy_for<T>(IInterceptor interceptor)
  {
    return new ProxyGenerator().CreateInterfaceProxyWithoutTarget<T>(interceptor);
  }
}

internal class LazyLoadedInterceptor<T> : IInterceptor
{
  private readonly Func<T> get_the_implementation;

  public LazyLoadedInterceptor(Func<T> get_the_implementation)
  {
    this.get_the_implementation = get_the_implementation;
  }

  public void Intercept(IInvocation invocation)
  {
    var method = invocation.GetConcreteMethodInvocationTarget();
    invocation.ReturnValue = method.Invoke(get_the_implementation(), invocation.Arguments);
  }
}

public static class func_extensions
{
  public static Func<T> memorize<T>(this Func<T> item) where T : class
  {
    T the_implementation = null;
    return () => {
      if (null == the_implementation) {
         the_implementation = item();
      }
      return the_implementation;
    };
  }
}

resolve is a static gateway to the underlying IDependencyRegistry

csharp designpatterns