A hybrid of Proxy and .
“The proxy pattern makes the clients of a component communicate with a representative rather than to the component itself. Introducing such a placeholder can serve many purposes, including enhanced efficiency, easier access and protection from unauthorized access.”
The Decorator pattern serves to attach “additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality.”
public interface IHotelGateway { IHotel GetHotel(string Destination, string HotelCode); } public class HotelCacheGateway: IHotelGateway { private IHotelGateway store = null; public HotelCacheGateway(IHotelGateway Store) { store = Store; } public IHotel GetHotel(string Destination, string HotelCode) { IHotel hotel = Cache[GenerateKey(Destination, HotelCode)] as IHotel; if (null != hotel) return hotel; hotel = store.GetHotel(Destination, HotelCode); if (null != hotel) Cache[GenerateKey(Destination, HotelCode)] = hotel; return hotel; } }
Then, instead of asking the Data Access Layer (DAL) for an object directly, the client code can ask the Decorating Proxy which will either
retrieve the object from a cache or delegate the call to the DAL.
IHotelGateway proxy = null, dal = null; dal = new SqlHotelGateway(connection_string); proxy = new HotelCacheGateway(dal); IHotel hotel = proxy.GetHotel(destination, hotelcode);
Or, better still, the creation of the Proxy and DAL can be encapsulated in a Factory Method which keeps the client from having to even know about the Proxy.
public class HotelGatewayFactory { private const string ConnectionString = "..."; public static IHotelGateway Create() { IHotelGateway proxy = null, dal = null; dal = new SqlHotelGateway(ConnectionString); proxy = new HotelCacheGateway(dal); return proxy; } } IHotelGateway gateway = HotelGatewayFactory.Create(); IHotel hotel = gateway.GetHotel(destination, hotelcode);
using System; using System.Web; using System.Web.Caching; // minimal implementation - a better implementation would allow for dependency and expiration control public class CacheManager { private static CacheManager singleton = null; private CacheManager() { } public static CacheManager Instance { get { if (null == singleton) singleton = new CacheManager(); return singleton; } } public static void SetItem(string key, object value) { HttpContext.Current.Cache[key] = value; } public static object GetItem(string key) { return HttpContext.Current.Cache[key]; } } using System; public interface IHotelGateway { IHotel GetHotel(string Destination, string HotelCode); } using System; /* -- note lack of dependency on System.Web.Caching -- */ public class HotelCacheGateway: IHotelGateway { private IHotelGateway store = null; public HotelCacheGateway(IHotelGateway Store) { store = Store; } public IHotel GetHotel(string Destination, string HotelCode) { IHotel hotel = CacheManager.Instance.GetItem(GenerateKey(Destination, HotelCode)) as IHotel; if (null != hotel) return hotel; hotel = store.GetHotel(Destination, HotelCode); if (null != hotel) CacheManager.Instance.SetItem(GenerateKey(Destination, HotelCode), hotel); return hotel; } private string GenerateKey(string Destination, string HotelCode) { return string.Format("Hotel:{1}-{0}", Destination, HotelCode); } } using System; using System.Data; using System.Data.SqlClient; public class SqlHotelGateway: IHotelGateway { private string ConnectionString = null; public SqlHotelGateway(string ConnString) { ConnectionString = ConnString; } public IHotel GetHotel(string Destination, string HotelCode) { IHotel hotel = new Hotel(Destination, HotelCode); using (SqlConnection db = new SqlConnection(ConnectionString)) { SqlCommand sp = new SqlCommand(db); sp.CommandType = CommandType.StoredProcedure; sp.CommandText = "usp_GetHotel"; sp.Parameters.Add(new SqlParameter("", Destination)); sp.Parameters.Add(new SqlParameter("", HotelCode)); using (SqlDataReader rdr = sp.ExecuteReader()) { while (rdr.Read()) { hotel.Description = rdr["Description"].ToString(); hotel.SetContact(rdr["ContactName"].ToString(), rdr["ContactEmail"].ToString()); hotel.RoomCount = DBNull.Value == rdr["NumberOfRooms"] ? -1 : int.Parse(rdr["NumberOfRooms"]); } } } return hotel; } } using System; public class HotelGatewayFactory { private const string ConnectionString = "..."; public static IHotelGateway Create() { IHotelGateway proxy = null, dal = null; dal = new SqlHotelGateway(ConnectionString); proxy = new HotelCacheGateway(dal); return proxy; } } // Client code IHotelGateway gateway = HotelGatewayFactory.Create(); IHotel hotel = gateway.GetHotel(destination, hotelcode);The main thing to notice about the code above is that the dependencies on specific parts of the framework (like System.Web and System.Data) are limited only to those objects that need to work with them directly. This makes swapping out implementations easier because the dependenices are localized. Changing the CacheManager to work with the file system would not affect client code one bit. Likewise, a migration to an Oracle backend would only affect one class.
Of course, this also assumes certain things about how you package the classes into assemblies. For instance, if you package these classes into one monolithic assembly, then while most code maintenance benefits remain, you will force a recompilation every time you add a new Decorating Proxy that the Factory class needs to know about. This pattern forces you to separate the Factory into a separate assembly so that you minimize the impact of changes. Of course, having an assembly with one class — especially such a small one — is lame, so I would advocate packaging all or several factory classes together into an assembly that, in essence, defines the API for the application. If this sounds familiar, it’s probably because it sounds a lot like Martin Fowler’s Service Layer pattern from , albeit at a lower level. Service Layer is more of a UI layer pattern. This pattern is more of a Data Access Service Layer which defines a common interface for accessing data without requiring the caller to care about implementation details like whether or not the data is being retrieved from a cache or the backing data store.