Conceptual Contours
Sometimes people chop functionality fine to allow flexible combination. Sometimes they lump it
large to encapsulate complexity. Sometimes they seek a consistent granularity, making all classes
and operations to a similar scale. These are oversimplifications that don't work well as general
rules. But they are motivated by a basic set of problems.
When elements of a model or design are embedded in a monolithic construct, their
functionality gets duplicated. The external interface doesn't say everything a client
might care about. Their meaning is hard to understand, because different concepts are
mixed together.
On the other hand, breaking down classes and methods can pointlessly complicate the
client, forcing client objects to understand how tiny pieces fit together. Worse, a
concept can be lost completely. Half of a uranium atom is not uranium. And of course, it
isn't just grain size that counts, but just where the grain runs.
Cookbook rules don't work. But there is a logical consistency deep in most domains, or else they
would not be viable in their own sphere. This is not to say that domains are perfectly consistent,
and certainly the ways people talk about them are not consistent. But there is rhyme and reason
somewhere, or else modeling would be pointless. Because of this underlying consistency, when we
find a model that resonates with some part of the domain, it is more likely to be consistent with
other parts that we discover later. Sometimes the new discovery isn't easy for the model to adapt
to, in which case we refactor to deeper insight, and hope to conform to the
next
discovery.
This is one reason why repeated refactoring eventually leads to suppleness. The
CONCEPTUAL
CONTOURS
emerge as the code is adapted to newly understood concepts or requirements.
The twin fundamentals of high cohesion and low coupling play a role in design at all scales, from
individual methods up through classes and
MODULES
to large-scale structures (see Chapter 16).
These two principles apply to concepts as much as to code. To avoid slipping into a mechanistic
view of them, temper your technical thinking by frequently touching base with your intuition for
the domain. With each decision, ask yourself, "Is this an expedient based on a particular set of
relationships in the current model and code, or does it echo some contour of the underlying
domain?"
Find the conceptually meaningful unit of functionality, and the resulting design will be both flexible
and understandable. For example, if an "addition" of two objects has a coherent meaning in the
domain, then implement methods at that level. Don't break the
add()
into two steps. Don't
proceed to the next step within the same operation. On a slightly larger scale, each object should
be a single complete concept, a "
WHOLE VALUE
."
[1]
[1]
The
WHOLE VALUE
pattern, by Ward Cunningham.
By the same token, there are areas in any domain where detail isn't interesting to the kind of
people the software serves. The users of our hypothetical paint mixing application don't add red
pigment or blue pigment; they combine complete paints, which contain all three pigments.
Clumping things that don't need to be dissected or rearranged avoids clutter and makes it easier
to see the elements that really are meant to recombine. If our users' physical equipment allowed
individual pigments to be added, the domain would be altered, and the individual pigments might
be manipulated. A paint chemist would need still finer control, which would involve a whole other
analysis, probably producing a much more detailed model of the makeup of paint than our
abstracted pigment color that serves paint mixing. But it is simply irrelevant to anyone involved in
the paint mixing application project.
Therefore:
Do'stlaringiz bilan baham: |