I've been playing around with some new patterns for n-layer data access, and came across one that seems very flexible and easy to implement. Basically, I needed a solution that could make various data layers pluggable/swapabale on the fly - i.e., base data access from DB, distributed caching, local caching, etc.
The code below is easily reused and incredibly efficient - only a few ticks longer than my previous entirely-hard-coded solution.
How does this look? Is there any way that this could be implemented better? Any general thoughts or critiques? Any input from those who have used similar patterns?
Base Class:
public class DataAdapterFactory<T> where T : class
{
private DataAdapter<T> Adapter;
public DataAdapterFactory(DataAdapterBase<T> Base)
{
Adapter = Base;
}
public void Extend<U>() where U : DataAdapterExtender<T>, T, new()
{
DataAdapterExtender<T> Extender = new U();
Extender.BaseAdapter = Adapter as T;
Adapter = Extender;
}
public T GetAdapter()
{
return Adapter as T;
}
}
public class DataAdapter<T> where T : class { }
public class DataAdapterBase<T> : DataAdapter<T> where T : class { }
public class DataAdapterExtender<T> : DataAdapter<T> where T : class
{
public T BaseAdapter;
}
Implementing in the DAL:
// base interface defines methods
public interface IMyDataAdapter
{
string GetString();
}
// base sql adapter
public class SqlDataAdapter : DataAdapterBase<IMyDataAdapter>, IMyDataAdapter
{
public string GetString()
{
return "SQL";
}
}
// provides cache support
public class DistributedCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
public string GetString()
{
return BaseAdapter.GetString() + ", Distributed Cache";
}
}
// provides local cache support
public class LocalCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
public string GetString()
{
return BaseAdapter.GetString() + ", Local Cache";
}
}
Accessing Data:
public IMyDataAdapter GetAdapter()
{
// create adapter based on SqlDataAdapter
DataAdapterFactory<IMyDataAdapter> factory = new DataAdapterFactory<IMyDataAdapter>(new SqlDataAdapter());
// implement distributed cache
factory.Extend<DistributedCacheExtender>();
// implement local cache
factory.Extend<LocalCacheExtender>();
return factory.GetAdapter();
}
Using the factory above, any combination of base adapters and extenders (Extend<>() must be called in the order of execution) can be used easily and seamlessly on the fly via the interface, with the business layer knowing nothing of the implementation.
In this case above, calling GetString() would result in "SQL, Distributed Cache, Local Cache". In a real world scenario, the local cache would be called first. If an item isn't there, we'd head over to the distributed cache, and if it isn't there, we'd get it from the DB - and any module could be swapped in or out as needed depending on the obejct, user, etc.