Wednesday, August 5, 2009

When not to use method overload

Method overloading is taught in OOP 101, but there are times when it should be avoided like plague. Let me explain ...

During a recent implementation of Caching solution, I had to create a CacheManager which could support the creation and management of different types of Cache types - say file system based, memory based, db based etc. Each cache type comes with different options. For example, file based system could monitor a file as a dependency, which the DB based cache does not.

A typical example of different overloads is as follows ...

interface ICache {
    void Add<T>(string key, T value)
    void Add<T>(string key, T value, string filename);
    void Add<T>(string key, T value, DateTime absoluteExpiration);
    void Add<T>(string key, T value, CacheDependency dependencies);
    void Add<T>(string key, T value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration);
    void Add<T>(string key, T value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
    CacheItemUpdateCallback onUpdateCallback);
    void Add<T>(string key, T value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
    CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
}

So how do we create an efficient caching solution without creating overloads for each supported options by a cache type.

The solution is to create a CacheOptions class that has a union of all options supported by all the cache types. So the example above can be rewritten as follows ...

interface ICache {
    void Add<T>(string key, T value, CacheOptions options);
}
class CacheOptions {
    public string FileName { get; set; }
    public DateTime AbsoluteExpiration { get; set; }
    public TimeSpan SlidingExpiration { get; set; }
    public CacheDependency Dependecy { get; set; }
    public CacheItemPriority priority { get; set; }
    public CacheItemUpdateCallback OnUpdateCallback { get; set; }
}

This does a few things for us ...

  1. Simplify ICache interface, let each implementation work with the CacheOptions
  2. Everytime we have a new Cache type, we don't have to modify the interface ICache, and thereby the other implementations
  3. We can also initialize (with non-null values) some properties which are optional parameters. This results in cleaner code ...

Before ...

SomeCahce.Add("key", new object(), new CacheOptions() { AbsoluteExpiration = DateTime.MaxValue });

After ...

class CacheOptions {
    public CacheOptions() {
        // Default values
        AbsoluteExpiration = DateTime.MaxValue;
    }
    ...
}
SomeCahce.Add("key", new object(), new CacheOptions());