Saturday, May 28, 2011

Lazy loading: Generic Proxy and Null Object patterns

Most developers will be familiar with the concept of lazy loading, and in particular the use of proxies to support this. I have a simple implementation of the Proxy pattern in the code base I work most with, using a basic generic interface approach.

Firs then, the state proxy interfaces, with a single method to retrieve state on demand and three flavours - one to three generic types.

1:  #region State proxy interface  
2:    
3:  public interface IStateProxy<R> {  
4:    R ObtainState();  
5:  }  
6:    
7:  public interface IStateProxy<R, S> {  
8:    R ObtainState(S arg0);  
9:  }  
10:    
11:  public interface IStateProxy<R, S, T> {  
12:    R ObtainState(S arg0, T arg1);  
13:  }  
14:    
15:  #endregion  

It's expected that concrete implementations of these interfaces are created at some time and injected into the object that desires to use their services. One such example is shown below - the two type variant, returning one type and accepting a different type as an argument.

1:  #region State proxy  
2:  [Serializable]  
3:  internal class HomeLoanStateProxy :   
4:      IStateProxy<ILoanOffsetInstrument, IHomeLoan> {  
5:    public ILoanOffsetInstrument ObtainState(IHomeLoan loan) {  
6:      ILoanOffsetInstrument result;  
7:      try {  
8:        result = AgentFactory.Instance.CreateMappedAgent<AccountsAgent>().DeriveLoanInstrument(loan);  
9:      }  
10:      catch (Exception ex) {  
11:        LogFacade.LogError(this, string.Format("Failed to gather loan instrument information for {0}", loan.Number), ex);  
12:        result = LoanOffsetInstrument.Empty();  
13:      }  
14:      return result;  
15:    }  
16:  }  
17:  #endregion  

The example proxy takes a Null Object pattern approach to exceptions during processing, and aggressively so. If, for some reason, the proxy fails during execution, a log event is generated, and the return object is set to an instance of a LoanOffsetInstrument that has benign content. This way, the proxy dependent can always rely on a non null result.

Then of course, the proxy consumer/dependent itself.

1:  public override ILoanOffsetInstrument OffsetInstrument {  
2:    get {  
3:      if (mInstrument.IsEmpty() && StateProxy != null)  
4:        mInstrument = StateProxy.ObtainState(this);  
5:      return mInstrument;  
6:    }   
7:    protected set {  
8:      mInstrument = value;  
9:    }  
10:  }  

While writing this, it occurred to me that:
  • It bears some similarities to the Lazy<T> previous post, in terms of intent
  • A proxy might be better implemented with a proper dynamic proxy approach - something like Castle Dynamic Proxy for example
  • The proxy implementation should take care of the state it serves up; there is little need for it to be stored as part of the dependent object (this should have been quite obvious to me - the proxy is even marked serializable, as at some point in it's life it is sent out of process along with its container)
  • The proxy should be more aware of its 'exception state' - that is, it should know if it has failed before and needs to try and retrieve state again; it should also know when to give up - that is, not keep pathologically attempting to retrieve an object if the retrieval keeps failing 
  • The dependent implementation could be made simpler on the back of this, with no need to have a property setter, and no local state being held:
1:  public override ILoanOffsetInstrument OffsetInstrument {  
2:    get {  
3:      return StateProxy == null ? null : StateProxy.ObtainState(this);    
4:    }     
5:  }  

That's almost right, but it does not honour the Null Object pattern intent of the state proxy, so we'd probably have to return an "Empty" object. But this is just doodling :-)

No comments: