Original Post:
I've fallen in to a pattern of what feels like excessive delegation,
probably helped along academically.
Cohesion is very important in OO development because it is the basis of
logical indivisibility which is, in turn, the basis of peer-to-per
collaboration. Good cohesion usually requires the class abstraction be
quite simple.
Alas, entities in problem spaces other than the computing space tend not
to be simple. So we need to simplify them. There are are basically
only two techniques for doing that: subclassing and delegation.
Unfortunately, subclassing has some disadvantages.
One is that it is a static relation so it can't be modified without
touching the code. Delegation, though, can be modified through dynamic
instantiation of associations at run-time.
A more serious problem is that OO subclassing, unlike data modeling's
version, does not really resolve the cohesion problem. That's because
all subclass members are also members of the root superclass. Therefore
all of the subclass specializations are represented in the membership of
the root superclass. So large subclassing trees usually indicate a lack
of cohesion for the root superclass (i.e., the tree as a whole is
serving too many masters).
On the upside, OO subclassing has some advantages than more than
compensate for the disadvantages so long as the tree is fairly simple
and the context is right. One advantage is collecting commonality among
very similar entities. But the biggee is inclusion polymorphism that
allows one to substitute different behavior implementations
transparently at run-time. The entire GoF set of patterns is based upon
this characteristic.
The bottom line is that one should seek to use delegation over
subclassing except in those situations where subclassing truly shines.
Has anyone got any wise words on detecting when it is appropriate and when
it isn't? I'm aware of responsibility. It's just that the request usually
comes from one end of the application and the ultimate responsibility is at
the other!! And then you get code which is 90% passing the buck!
Use subclassing when there is a natural taxonomy in the problem space.
When in doubt mimic the problem space. In reality, though, behavior
taxonomies are extremely rare in most customer problem spaces, so this
effectively means data taxonomies.
Use subclassing when groups of entities are very similar but differ in
minor ways. Even then, look for way to employ parametric polymorphism
(i.e., express the differences in terms of data values rather than
specializations) instead. Also look for ways to separate out groups of
properties in terms of roles. Separation of roles may not eliminate
subclassing but it can often simplify it substantially (e.g., a huge
tree is refactored into two or more much smaller role trees). The GoF
State and Strategy patterns are classic examples of this sort of
simplification by role extraction.
Use subclassing when you need to substitute behaviors dynamically. If
you look closely at the GoF patterns they all share a common "pattern"
-- they all implement a simple association where participation in the
association is inherently dynamic (determined at run-time) and it is
complicated. IOW, the GoF patterns are reifications of relationships
that are too complicated to describe with simple associations.
Use subclassing to eliminate conditional relationships. Conditional
relationships require extra code to be written in every context where
the relationship is navigated. For example, a simple representation of
a hierarchical taxonomy is:
starts at 1 0..1 child of
[Tree] ----------------- [Node] ---------------+
0..1 root of | 0..* |
| parent of |
| |
+------------------+
One can eliminate all of the conditional relationships via:
* parent of
[Node] -------------------------+
A |
| |
+----------+------------+ |
| | |
[Leaf] [Non-Leaf] ---------+
A 1 child of
|
+-------+-------+
| | 1 starts at
[Intermediate] [Root] ---------------- [Tree]
base of 1
Because of the class declarations this will be more LOC but the number
of executable statements will be substantially fewer, which should
improve reliability. In most practical situations there will also be
other justifications for the more elaborate version (e.g., a Leaf may
have an item description in a POS application while the others don't).
Use delegation for everything else.
As far as your concern about lots of code to pass the buck is concerned,
that suggests something else is wrong. Typically delegation involves a
new class and a relationship. The collaborations should still be
peer-to-peer. That is, whoever would have accessed the property in the
original class would now navigate to the new class owning the property
and access it directly there. Basically one has originally:
[BigHonker]
+ doIt1
+ doIt2
+ doIt3
+ doIt4
which, after delegation, goes to
1 delegates to 1
[Honker] -------------------- [NewHonker]
+ doIt1 + doIt3
+ doIt2 + doIt4
IOW, you don't want the client to invoke BigHonker.doIt4 and have that
method just invoke NewHonker.doIt4; the client should invoke
NewHonker.doIt4 directly.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
(e-mail address removed)
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
(888)-OOA-PATH