This was just a little exercise to explore inheritance vs. composition in javascript. I've been reading and thinking about this topic for a bit and got sick of all the contrived examples. But this opportunity presented itself randomly, so I took advantage. Here's some background.
This is a diff produced by forking @mikeal's couchie library and changing it to be built on top of my little storage lib. For this example I took the approach of composing the Storage lib inside Couchie (as opposed to using inheritance). You may want to familiarize yourself with my storage library and @mikeal's couchie library. The repo for the above changes is in a branch called "composed" on my couchie fork.
Not significantly shorter - Not much code was eliminated from the couchie lib. There are a few reasons for this. The Couchie api is different. All the Couchie methods use the node async callback convention. This is on purpose, even though the localStorage is synchronous, since this lib is supposed to mimic a real couchdb client. So we can't avoid these changes. Also Couchie has several additional methods. The only method shared with Storage is get
. And even that has be overridden to add some desired functionality.
In fact the truth of is we also picked up all the storage code. Depending on what you're looking for, this might seem unnecessary. But both libs are really small, especially minified and gzipped. And adding storage gives us some other nice things.
Gained some functionality - By deriving Couchie from Storage, we get all of the underlying functionality of Storage "for free". So we don't have to worry about serializing and unserializing everywhere. We don't have to worry about prefixing everywhere.
Missing some freebies - Because of the way we're composing storage, as a "private" var on Couchie, we only get added functionality when we delegate. So we get the freebies mentioned above. But we don't pick up any additional features of storage, e.g. set
, del
, compound keys, etc. But all of this is still available if we want it. And it would take minor changes to expose them.
Changed some functionality - Something way less obvious that's happening here, is that when we derive from storage, we introduce subtle changes to how Couchie works.
The storage lib prefixes keys with this._prefix + '::'
. This is a change from the Couchie prefixing. It's doubtful that the "::" will make a difference, but it's something to be aware of. You would not be able to use this lib with data stored by old versions of Couchie. You could fix this, but you'd have to change the storage lib.
Harder to take advantage of all features - Another problem arises if we want to take advantage of the compound key feature of Storage. Because the Couchie methods expect a callback in the last position, we would have to do surgery on our arguments
each time in order to pass the variable list of keys to the underlying Storage methods.
This was pretty straight forward. We end up with code we don't use. But we pick up some nice freebies. And I definitely like the composition method over inheritance. This method can be refined further I think. There are still some problems to solve. But it was easy to accomplish and easy to reason about, unlike the inheritance-based version.
The changes to backward compatibility are also problematic. They are arguably already a problem in Couchie. You can't change the key generation method without breaking back compat. This may be the deal-breaker. Considering the circumstances, I don't think re-using storage was necessary at all for something like Couchie. But still an interesting exercise.
You can checkout the inheritance-based version over here.