Playing with functional programming gave me a lot of new insights into object-oriented design. I like the lightweight “interfaces” of functional signatures, and I love the idea of really small and reusable functions. However in most object-oriented systems I’ve seen Header Interfaces and composition through inheritance, or directly mashing more code in one class.

Thanks a great deal to Mark Seeman, I’ve discovered that adhering to SOLID principles can produce object-oriented code which has many of the properties I liked in the functional style. I would almost say that purely SOLID OOD is equivalent to functional design, but that’s enough material for another article.

As you refactor your classes closer to the principles, you will see many patterns emerging. I’d like to have a look at two paticular ones.

Although Decorator and Composite patterns are quite well-known in theory, in practice they are often overlooked as a solution to common problems.

Let’s have a look at some code, a simple feature request, and what we may call refactoring to patterns (although in short term rather than what Joshua Kerievsky describes in his book).

Caching a Storage Reader with a Decorator

Suppose we have a storage reader interface:

public interface IReader
{
  StoredItem Read(string key);
}

Also we already have a simple implementation for reading items from DB storage

public sealed class DbReader : IReader
{
  private readonly IDatabaseContext context;
  
  public DbReader(IDatabaseContext context)
  {
    this.context = context;
  }
  
  public StoredItem Read(string key)
  {
    return context.StoredItems
                  .FirstOrDefault(item => item.Key == key);
  }
}

Now we get a feature request to add in-memory caching to our reader, because our database is overloaded, it hinders performance, while the application server has plenty of time and free memory.

The obvious and easiest way would be for example to add a Dictionary<string, StoredItem> to the DbReader class and then in the Read method check its contents prior to reading from the database. Such easy implementation of the Read method would look like this:

// Example of bad/naive code, you probably don't want to do this!
public StoredItem Read(string key)
{
  StoredItem result;
  // Check the dictionary first.
  // If the key is already cached, return the value.
  if (cache.TryGetValue(key, out result))
  {
    return result;
  }

  // Key not cached, we have to have a look in the DB.
  result = context.DbSet<StoredItem>
                  .FirstOrDefault(item => item.Key == key);

  // If the was in DB, we probably want to cache it for the next read.
  if (result != null)
  {
    cache[key] = result;
  }

  return result;
}

As you can see, the trivial one-expression method immediatelly bloated into 10+ lines, multiple-branches monstrosity. Of course I’m overstating here a bit, but it was a trivial example code and you can already see an order of magnitude increase in lines count and complexity because of a simple feature.

This class now also has multiple responsibilities. In addition to reading a value from the database, this implementation handles reading from cache and updating it, as well as maintaing the cache itself.

Most code I’ve encountered in practice does exactly this and stops there.

It’s not a big deal yet from maintenance perspective, I give you that, but we’re purists here (right? :) ) and staying on this level would prevent us from seeing the more general patterns emerging here.

To clean the solution up a bit, let’s separate the cache into its own class.

public sealed class StoredItemCache
{
  private readonly IDictionary<string, StoredItem> cache;

  public StoredItemCache()
  {
    this.cache = new Dictionary<string, StoredItem>();
  }

  public StoredItem GetItem(string key)
  {
    StoredItem result;
    cache.TryGetValue(key, out result);
    return result;
  }

  public void SetItem(string key, StoredItem item)
  {
    cache[key] = item;
  }
}

This encapsulates the dictionary and gives clearer cache interface by publishing only GetItem and SetItem methods. Not only it makes the cache easier to use and reuse, it also allows for easier changes to the in-memory store method. Maybe in the future we’d like to use MemoryCache or ConcurrentDictionary instead.

Our DbReader could use this class as a dependency for handling the cache, but it really only takes care of one of the responsibilities, maintaining the cache. The DbReader.Read method would still have to make all the decisions.

Before we modify the solution even further, notice one thing. The signature of GetItem is the same as the one of IReader.Read method. The cache can implement IReader interface directly!

public sealed class StoredItemCache : IReader
{      
  public StoredItem Read(string key)
  {
    StoredItem result;
    cache.TryGetValue(key, out result);
    return result;
  }

  // The rest of the class stays the same.
}

It may seem weird at first, but such fine-grained separation helps with Interface Segregation Principle and leads to Role Interfaces which I personally like more than the header interfaces.

Now that the cache implements IReader interface, we may have another look at its relationship with the DbReader class.

The original requirement was to add caching to the database reader. According to the Open/Closed Principle, a class should be open to extension, but closed to modification. We did modify the DbReader implementation, so we may have done something wrong. We can extend the class without modification by decorating it. We can rewrite it as follows:

public sealed class CachingReader : IReader
{
  private readonly IDictionary<string, StoredItem> cache;
  private readonly IReader decoratedReader;

  public CachingReader(IReader decoratedReader)
  {
    this.cache = new Dictionary<string, StoredItem>();
    this.decoratedReader = decoratedReader;
  }

  public StoredItem Read(string key)
  {
    StoredItem result;
    if (cache.TryGetValue(key, out result))
    {
      return result;
    }

    // Delegate the cache-miss scenario to the wrapped reader.
    result = decoratedReader.Read(key);

    if (result != null)
    {
      cache[key] = result;
    }

    return result;
  }
}

It looks about as complicated as our first caching attempt and it adds an extra class, but it has its benefits.

Most importantly, we haven’t modified the original implementation - this makes it easier on other DbReader users, who thus aren’t forced into caching, but can optionally add it using this decorator.

Also as long as you always program against interfaces (like IReader instead of concrete DbReader), such decoration can take place only in your composition root without a single change to the rest of the application code.

I have used composition to extend the original reader, but in this case it would also be viable to use class inheritance instead (provided DbReader isn’t sealed and the Read method is virtual). However inheritance wouldn’t work for the following scenario, and I suggest favoring composition over inheritance anyway.

From Decorator to Composite

You’ve seen creating a class extension via Decorator, adding the caching logic around a delegated call to the wrapped DbReader, but what if I want to reuse my new cache class as a stand-alone in-memory storage instead of just a decorator, for example as a fake storage in tests?

In this case I can still implement the same interface, but I cannot delegate the call directly - in the stand-alone cases scenario there won’t be any decorated DbReader to attempt reading from another source. The item either is in the in-memory storage or it isn’t.

The implementation is straighforward:

public sealed class InMemoryReader : IReader
{
  private readonly IDictionary<string, StoredItem> cache;

  public InMemoryReader(IDictionary<string, StoredItem> cache)
  {
    this.cache = cache;
  }

  public StoredItem Read(string key)
  {
    StoredItem result;
    cache.TryGetValue(key, out result);
    return result;
  }
}

Notice two things: I have removed the SetItem method, and I’m passing the dictionary from the outside. The point in that is to adhere to the IReader interface more strictly. Somewhere in your application there is probably an IWriter with its DbWriter and a decorator to update the cache - in our case the shared dictionary. In practice I often implement this as a single class implementing both IReader and IWriter, and encapsulating the cache implementation. In this example however let’s keep the InMemoryReader as small as possible.

Such in-memory cache implementation is indeed simpler, but now how do I compose it together with the DbReader to cache the stored items?

The answer to this is the Composite pattern. It’s similar to Decorator from the outsite - it’s a special implementation of the same interface it wraps, but it owns more wrapped instances. In our case the cache and the database reader.

public sealed class CachingReader : IReader
{
  private readonly IReader cache;
  private readonly IReader wrappedReader;

  public CachingReader(IReader cache, IReader readerToBeCached)
  {
    this.cache = cache;
    this.wrappedReader = readerToBeCached;
  }

  public StoredItem Read(string key)
  {
    var result = cache.Read(key);
    // Cache miss, try the wrapped reader.
    if (result == null)
    {
      result = wrappedReader.Read(key);
    }
    return result;
  }
}

In composition root we can pass InMemoryReader as cache and DbReader as readerToBeCached. This allows us to use both types completely separately, but when necessary compose them to get the original requested caching functionality.

This is fairly solid (pun intended), but if you think about the Read method now, it’s a IReader.Read which tries to call one IReader.Read first and if it didn’t return anything, call another IReader.Read on another IReader.

This sounds like lazy collection reduction to me, so we can try to rewrite it as one. In the same step, we can further generalize the aggregate to accept more than two readers - for example you may want to have an in-memory cache, NoSQL cache and the SQL storage, or a NoSQL cache, SQL cache and a 3rd party service etc.

public sealed class CachingReader : IReader
{
  private readonly IEnumerable<IReader> readers;

  public CachingReader(IEnumerable<IReader> readers)
  {
    this. readers = readers;
  }

  public StoredItem Read(string key)
  {
   // The lazy evaluation here makes sure we only call
   // as many reades as we need to get a first valid value.
   return readers.Select(reader => reader.Read(key))
                 .FirstOrDefault(item => item != null);
  }
}

This implementation can take arbitrary number of individual and independent reader instances and when Read is called, it will try them one by one until an item is returned or we’ve tried all the readers.

Notice that the implementation complexity is back to one-expression method. Pretty neat, huh? The only responsibility of a composite is the orchestration - making sure the wrapped objects are called in a particular manner. And again, all changes to caching policies and implementations happen in the composition root, while the rest of the application stays completely intact.

Conclusion

SOLID principles can lead you to write reusable, single-purpose classes. Often this leads to emerging patterns that you may have heard of, but haven’t seen or used yourself.

In the example I’ve focused on Decorator and Composite, because in my opinion they embody the simplicity of functional interfaces in object-oriented world. There are of course many more manifestations of the similarities between object and functional approaches, if you take the time to discover the patterns.

I strongly recommend Mark’s blog and Pluralsight courses for further exploration.