In IT work – “it puts the database on its browser skin” – we often face sooner or later the problem of the gigantic object.
Some systems, especially “global monolith reboots”, just start there. Others just grow and grow and insensibly slide into it over time. The problem is that there are one or a few objects that are just plain central to the domain. To forestall ron and his marick quote, I’ll use the classic ‘order’ as my example here. Imagine a system that is meant to interact with orders all the way from manufacturing to stocking.
Planners give detailing to orders, then assign them to production facilities. Those folks use an app to update state right up to logistics. Logistics moves the orders from vendor to vendor, sometimes as simple as parcel delivery, sometimes shipping containers, and so on. Vendors use those orders to track their inventory, monitor their part of the pipeline, add/alter as their marketing changes shapes.
In beginner’s OO, we do a thing that olbs sometimes call “noun circling”. Go through the specs and circle all the nouns and call them objects. Noun-circling isn’t bad, per se. But it can be dangerous for the inexperienced, and as Bob Martin pointed out, inexperience is the norm. All of this is why I recommend that my intermediate oo friends invest heavily in understanding two closely related patterns.
These are the strategy pattern (from gof) and the function-specific pattern (from I made it up from so many owwies I have lived through). Strategy was originally designed for supporting variant algorithms for operations on an object. If there are two ways to calculate the gross weight of an order, the strategy calls for me to have two classes, one for each algorithm. These classes are inside the skin of order. Depending on domain, the order (or a client) chooses one, and assigns it as a member then when another client asks for the gross weight, the order uses its own strategy field to calculate the correct response.
This has nothing to do with solving the problem I laid out, but in fact, the exact same mechanism can be used in a number of ways. Forget two ways to gross weight. Consider two dramatically different algos, like compute gross weight and select best production line. We use the same basic mechanism, placing a class as a member of order, but we use those classes for completely different purposes. Our very large order object becomes smaller each time we do this – if we decompose reasonably well.
The first few times one implements strategy, one meets some delicate questions. How do I get the data to the algo object is a big one. How/when do I set or change strategy is another one. I have no set rules for this, I’m a stepwise experimenter by nature. You just have to try it different ways until it starts to feel tight. Do that often enough, and you’ll get quite good at it. I often find myself holding just one data field in the monster object: the key I need to find anything I want about an order. Individual strategies do the rest of the work. This works well, but can eventually cost you performance. Wait til it does, is my advice.
Now what about the function-specific object? This is what I call a kind of external strategy. Here, the order doesn’t keep its own strategy’s internally. Any strategy can be applied to any order by a client. (this is nothing more than a complex functor, an object that represents an actual function rather than a noun. Nothing new under the sun.)
Now push one more time. What if I make a class that a) is an order, but b) is customized for, say, plant-choosing. Its methods are tightly focused around all the things one needs to do to choose the right plant for building its order. It exposes few or even none of the underlying order data or its generic operations. Instead, it just does plant-selection. I have taken the key+functor idea of strategy and I have bundled several related strategies into an object “on top of” an order.
This is not rocket science. Most geekery is not rocket science. But it’s not the kind of thing a noob can see without prompting. And it’s not the kind of thing an intermediate can do without practice and experimentation. So the next time you see a very large object – lots of data, lots of operations – maybe you can get some new ideas for how to wrangle it.
A key insight: working this way means I no longer have to design either my order or my order’s underlying db in one massive step. I can always add new function-specific orders. I can keep running my tests against my old ones to prevent interaction effects and I can refactor my db right underneath all this without having to change any of the client code. The fundamental challenge of massive IT is to find ways to do it one step at a time. Strategy + function-specific are a start.