One of the most profound insights I have learnt about OO is that class design — the shaping of classes & types — is best informed by what processing needs to do, rather than the ‘kind’ of entities it goes between.
What we are talking about here is behavior, rather than trying to categorize entities at rest. Program code only acts by being executed; classes & interfaces (types) are a mechanism to despatch that execution to specific methods.
This shows that OO is really about active behavior, rather than any other arbitrary notions of classification.
But shouldn’t inheritance just follow Entity Properties?
At this point, we’ll address a common interjection — what about bean-style entities, and their properties? Surely inheritance should just follow these!
The answer here is that reading state is a behavior; and often a simpler one, amongst the more important (data processing & calculation) interactions an entity has to participate in. Class design must include all behaviors in modelling, and complex interactions are often the more important factor in guiding design.
An extra tip: Many young players assume writeability should go hand-in-hand with readable state; however writeable state is actually a much more specific & constrained behavior. For simple entities writeability normally implies read, but the reverse is not always true. If your v2 app needs to introduces calculated values or derived information, having designed all your entities to be writeable in v1 will come back as a design decision to bite you.
The Shape of the Machine
When considered on a basic level, the purpose of most software is to process & transform data. Even in data storage software — such as a database like Oracle — engine internals focus on processing index blocks, streaming join operations, and the like. Program code is always concerned with activity, never with rest.
Comparing algorithms — like quicksort, a hashmap, or a shortest-distance graph traversal algorithm — we find these have the same structure, regardless of what data they may be processing.
To sketch a picture: Software is a machine, and it’s design should be based on what it does. What the inputs look like when standing still may inform us, but it’s a secondary consideration.
Entities have Multiple Interactions
OO is a relatively modern invention. Though it became popular in the ’90s with C++ and Java, it’s powerful & complex enough that it’s only in the following decade that how best to use OO has really started to be understood.
And one of the struggles developers have with learning OO, is the plethora of trivial animal examples that fail to illustrate any significant behavior.
So we’ll break that paradigm here, with an Animal example that actually demonstrates multiple interactions & significant behavior — the kind of space real-world OO design needs to work in.
Let’s consider a Zoo. Following our naiive instincts and classifying animals biologically, this presents what might look like a good initial design:
If this were accepted & coding started, everything would work fine for the first interaction — say, feeding — as there would be no other conflicting inheritance requirements in the heirarchy. The limitations of the design would only begin to be noticed after the second or third collaboration needed to be “shoehorned” in.
The problem is that the multiple interactions a real entity model needs, are not effectively supported in the above design.
Let’s look at an alternate approach. This focuses first of all on “doing” within the Zoo:
What we see here is a completely different emphasis. This design identifies key “interaction roles” for the animal — Eats, Lives, and Displays — and defines these as the focus of interaction with overall Zoo activities (feeding, housing/ enclosures, and visitors).
This reflects the primary need of the Zoo to know how an Animal should be fed, enclosed, and promoted. These are the interactions required daily to actually manage & run the zoo. As we see, these behaviors are far more important than how the animals are classified.
While we don’t explicitly detail the Animal hierarchy in our model now, a hierarchy is possible — but we should avoid over-complicating. This will lead to an interesting topic later, the question of composition versus inheritance. But we’re focusing on the primacy of behavior, for now.
What this modelling diagram shows, is that interactions should be identified based on behavior. These can be implemented either via method overriding or composition, but what is crucial, is to identify them before putting an incompatible heirarchy in place.
Once interactions are identified, OO design can better focus on despatching behavior. When behaviors vary independently of the entity (or each other), this implies that behaviors could be factored out of the entity.
Factoring a behavior out makes the behavior a first-class object, and gives it its own type heirarchy. While the Animal entity now uses the behavior by composition, this gives us total freedom of OO techniques to implement & vary behavior; and enables reuse of it, by any animal entity.
Where we end up with this approach, is a new focus of OO classes & heirarchy on modelling behavior. The idea of entity being a monolithic element has been deprecated; instead, we are breaking down complexity into separate elements, which OO techniques can easily model.
This shows how applying a behavioral perspective can allow the core requirements of a system to be better revealed, and OO applied to implement them.
This allows a successful design where new Animals and even entire new interactions could easily be added.
‘Doing’ vs ‘Being’ in the Java Libraries
The Java libraries provide frequent demonstration of the principle of focusing on ‘doing’, rather than ‘being’. Class & interface design in many areas of the Java API focuses on streams, strategies, readers or providers; all of which are behavioral.
Even in the area of data storage — java.util Collections — which is most about ‘being’, the most key element of commonality in the API remains behavioral.
Collections offers an important & useful library of data structures. While many of these APIs are tied to the data structure, iteration is the most fundamental operation of all collections and this is abstracted as a behavior.
Having Iterator modelled as its own interface, enables Java code to iterate over any type of collection regardless of the underlying implementation. This is so important, that it’s almost regarded as a given; but it illustrates the important in OO of focusing commonality & reuse on behavior.
Perhaps, though, the best examples of behavioral OO design can be found in the java.io package.
Let’s consider java.io.File — surely a file should read & write? Actually, as we know, files are read & written via FileInputStream and FileOutputStream.
Instead of placing behavior on the entity, the Java IO library places key behavior in two separate classes.
This opens the door to the crowning glory of java.io: the ability to abstract, delegate and wrap streams. Summing up these 3 key achievements:
- abstraction & commonality: the ability to abstract input/ output streams across a variety of underlying mechanisms,
- delegation: the ability to delegate eg. BufferedInputStream delegating to a real FileInputStream,
- wrapping into higher-level objects: the ability to wrap low-level streams to higher-level objects; eg, InputStreamReader wrapping InputStream, or ZipFileInputStream wrapping FileInputStream.
The last concept — wrapping — seems to me, most powerful. This illustrates the ability to build high-level concepts (of great use) from lower-level building blocks.
All taken, java.io gives a great deal of power & immense design flexibility. This shows the power of design for ‘doing’ rather than ‘being’; none of this would be possible, if read() and write() had been implemented directly on File.
Summing up, many of the beginner difficulties with OO inheritance come from an excessive focus on “what it is”, not “what it does”. This confusion can be most effectively untangled by modelling interaction & behavior first, before considering entity inheritance.
Principle 1: Design for processing & behavior first, then design data at rest.
This article is part 1 of the Advanced OO Design series. Add your thoughts & comments now!