Blog root page
previous post in category
next post in category
Conditional ends on associations should be avoided whenever there is a reasonable alternative. There are a couple of reasons for this. One is that when a conditional association is navigated there will have to be check in the code to test if the relationship has actually been instantiated. Because one employs peer-to-peer collaboration in OO development, the peers have to traverse relationship paths through the Class Diagram. Thus the same relationship may be in many paths between different peer objects and that testing can result in significant overhead.
More important, though, is that very often more is involved than a simple test for a NULL pointer. Quite often there has to be an "else" clause to deal properly with the situation where the relationship is not instantiated. That is essentially exception processing and generally the solution will be simpler if one can avoid dealing explicitly with exceptions. So one should seek a way to express relationships without conditional ends. If one is successful, then the application will be more robust because one is enforcing problem space business rules and policies in the static structure of the application rather than in dynamic code.
As an example, consider the common example of a hierarchical tree:
0..1 navigates 1 0..1 child of [Client] ------------------- [Node] ------------+ | 0..* | | parent of | | | +----------------+
In this case every instantiated association will have two condition ends except for one and that one will have one conditional end. The actual code to implement this will become convoluted if the leaf Nodes in the tree are somehow different than the upper Nodes -- which is quite common for things like POS web sites where the hierarchy exists to navigate to the leaf items.
One way to get around this is by distinguishing between the types of nodes in the tree:
* parent of [Node] -------------------------+ A | | | +------------+-----------+ | | | | [Leaf] [Parent] -----------+ A 1 child of | +---------+--------+ | | [Root] [Intermediate] 1 | navigates through | | | entry for | 1 | [Client]
Now there are no conditional ends. All of the rules of a hierarchical structure have been explicitly expressed in through the subclassing and associations.
But wait! How does the Client know when it has reached a Leaf? The short answer is because the Leaf won't have any parent/child association. So the key question becomes: how is having no association different in practice from having a NULL instantiation of the association? The answer to that is complicated and it gets to the heart of the notion of enforcing business rules in static relationships.
The difference is that we have made having a parent/child relationship an intrinsic property of the entity; Leafs have no children but Parents do always. So if we have a "type" attribute for Node that indicates {LEAF, PARENT}, the client can check that during navigation. [Note that the type attribute is not semantically equivalent to the subclass type. For one thing, it does not distinguish Root vs. Intermediate. It could just as easily have been {NO_CHILDREN, CHILDREN}.]
But aren't we back to testing something to determine how to navigate? Not quite. The reason is that the semantics is not the same. We are testing an intrinsic property of the object, not the conditions under which two objects collaborate. That property is relevant to the nature of collaboration, not participation in the collaboration. While that is a subtle distinction, it has important implications in principle for the complexity and robustness of the dynamic solution.
To see this we must note how much better the second diagram describes the hierarchical tree than the first diagram. Basically we have been much more explicit about the way that hierarchical trees actually work. The Client is going to navigate the tree for some purpose. It may want to get to a particular Leaf. It may want to do a guided search where someone else selects the next node to traverse. It may want to do some complex processing at every Node, possibly even reordering the tree itself. (As an extreme example, consider the Parent Nodes are logical operators, the Leaf Nodes are logic states, the tree itself is an arbitrary RPN representation of a logical expression, and the goal is to reorganize it so that the Leafs are grouped into the minimum sum of products.)
These are quite disparate purposes and the processing will be quite different. Yet all of those algorithms can be expressed in terms of Node properties, regardless of the collaborations that "walk" the tree. More to the point, the mechanics of "walking" the tree is done the same way in all cases. What we have done is to convert the orthogonal issue of relationship navigation into a problem of manipulating intrinsic object properties.
The kernel issue here is that relationship instantiation is orthogonal to relationship navigation. We instantiate relationships to define participation (i.e., constrain the allowed participants in collaborations). We navigate relationships to execute peer-to-peer collaborations. Generally the rules and policies for participation are different than those for collaboration. Therefore, in principle, we want to separate and encapsulate those concerns because doing so will usually provide greater robustness.
Checking if an association is NULL is an instantiation issue and when objects collaborate they should only be concerned with the issues around collaboration. And collaboration is about accessing intrinsic knowledge and behavior responsibilities. Therefore, if we express any problem solution decisions in terms of intrinsic object properties, collaborating objects do not need to be concerned with instantiation issues and that is a Good Thing.
This all sounds nice, but does it have any practical significance? I assert that if you eliminate conditional association ends, your dynamic problem solution will very often be simpler. More important, when you have to change the solution it will be easier to do so. But such assertions defy proof because they are based upon practical experience. However, I can offer an example.
There is always some sort of processing associated with hierarchical trees. As I indicated above, such processing (object collaborations) may be profoundly different in different contexts. OTOH, the actual traversal mechanics is the same because it is dictated by the tree structure (relationships). An obvious way to manage complexity is to separate the traversal algorithm itself from the processing done as the Nodes are accessed. In the first diagram the only place to put that traversal algorithm is in the Client. In the second diagram a much more logical place for it is in the Root Node.
The significance of that will become even more apparent if there are multiple Clients that access the tree for different purposes. It would clearly be very fragile for each client to encode the traversal algorithm in addition to its own view of the processing; if the tree itself were modified (e.g., converted to a B-Tree), each client would have to be modified. That separation of traversal concerns only becomes obvious in the second diagram. IOW, removing the conditional ends creates a structure where the separation of disparate rules and policies becomes obvious.
A final cautionary thought. One should always evaluate ways to remove conditionality in association ends but that doesn't mean one should always do so. Sometimes the conditionality is a problem space imperative that one must level with. More to the point, though, is that there is a tradeoff. The second diagram above is more complicated in a static sense. While it may lead to a less complex or more robust dynamic solution, that benefit may not be worth the price of the additional complexity -- especially when the basic problem is very simple.
Blog root page
previous post in category
next post in category