« 2.0 My bookshelf for OOA/D/P | Main | 2.3 Invariants: Expressions »

June 03, 2004

1.0 Invariants in OOA/D

Abstraction is one of the fundamental characterisitcs of OO develoment. Abstraction of problem space entities provides long-term stability to software structure. Abstraction also allows a flexible view of logical indivisibilty that permits peer-to-peer collaboration, which eliminates the hierarchical dependencies that led to classic spaghetti code. In my opinion, though, the most important benefit of abstraction lies in the ability to capture problem space invariants.

The basic idea here is that one should model and encode the invariants of the problem space while describing detailed differences with data values. This has important implications for maintainability because invariants shouldn't change over time so one has long-term structural stability. A more subtle but equally important benefit lies the in reduction of code size. Invariants are reflected in generic behaviors that work in exactly the same way in all situations so there is no need to deal with special situations in the code. Eliminating such specializations can drastically reduce the number of decisions and the number of executable statements. That, in turn, improves reliability because the number of opportunities for inserting defects is reduced.

However, abstraction alone is insufficient on its own to get the job done. To be effective one has to be able to describe differences in terms of data. Thus one needs parametric polymorphism to so that the data values can influence the results of executing generic behaviors. Traditionally parametric polymorphism has been done through method arguments. The method operates on the arguments and different results are obtained by changing the values of the parameters passed.

OO development provides much more powerful and versatile mechanisms for parametric polymorphism than simple method parameter passing. When applied to invariants one has what amounts to a design pattern:

[GenericObject] ------------------- [Specification]

Here [GenericObject] is an object with some generic behavior and [Specification] holds data attributes that will act as parameters for the generic behavior. The power results from the fact that the parameters can be dynamically changed based upon context in two ways: one can change the values of the attributes in [Specification] via setter access or one can substitute [Specification] instances by dynamically instantiating the relationship between [GenericObject] and [Specification]

A secondary benefit here is that the concerns for defining the context can be separated from the concerns of collaboration when some client object accesses the behavior in a GenericObject. That is, the collaborating object does not have to understand the rules and policies for which data values are appropriate to the context. If the values were passed as method arguments, then the caller would also be responsible for providing the correct values.

This is a subject that screams for examples, so there are several examples in related posts within this category. They can be recognized by the tile "Invariants: example name". I would caution, though, that the examples are designed for clarity in exposing the basic ideas. So they may not be as complete, efficient, or elegant as one might expect in a real solution.

Of course there is no free lunch in software development so there can be a price for using invariants, especially at a small scale like abstracting register read/write access or arithmetic expression evaluation. It can result in more classes and instances or more complex Factory pattern code. Thus there is always a trade-off between providing invariant infrastructure and the need for dealing with volatile requirements.

In my experience opting for invariants is usually justified when they are readily recognizable in the problem space. That's because whoever designed the problem space already saw value in them for that context. So one should always look for them. It is easy enough to decide the infrastructure would be overkill for the particular problem in hand. It is a lot harder to refactor after one has invested enough effort to encoding details to realize that an invariant would have been a better choice.

Posted by HS in Invariants and Parametric Polymorphism | Permalink