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.
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:
Also we already have a simple implementation for reading items from DB storage
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:
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.
This encapsulates the dictionary and gives clearer cache interface by publishing only
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
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!
Now that the cache implements
IReader interface, we may have another look at its relationship with the
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:
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
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:
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
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.
In composition root we can pass
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
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.
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.
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.