Managing Change through OOAD
By Ken Sipe
All systems change during
their life cycles. This must be
borne in mind when
developing systems expected to last longer than the first version.
-- Ivar Jacobson
When we think of change management in software development
terms, we usually tend to think of requirements management with its
requirements trace matrix or code management through version control systems.
Visions of constraining a stake holder, to the first thought they mistakenly
put to paper, then making them pay through endless levels of bureaucracy for
any change they may have come to mind. Then of course after realizing the
requirements keep changing, the lesson on version code management would be in
dire need. So we branch any new idea then within the last couple of days
before release of the software, we merge 10 of 12 branches and releasing.
Sounds simple enough! Well while the project managers and the business
analysts learn new techniques on how to drive us crazy, lets examine ways to
develop object-oriented software which is resilient to change.
Ive often consider software development or the act of
designing and writing software to be closer to philosophy than science of
course it doesnt help that I just finished reading Zen and the art of
motorcycle maintenance. The fact is there should be a constant voice
in your head as youre developing asking the question what is the likelihood
of change here? , the other voice should be asking you about the level
abstraction you are looking at and should you look at the problem another way,
using Phaedrus knife so to speak. It is more about how you analyze code which
makes you good at coding It is more about understanding the details of a
certain level of abstraction. It is more about understanding certain
principles of development and applying them. Good object-oriented code is less
about structure and more a matter of behavior. Learning object-oriented
concepts such as inheritance, or encapsulation is just the start. Lets look
at several principles of correct application to improve the quality of our
software solutions, and what effects these principles have on change
Implementing your Analysis
One significant problem I run into is what I refer to as
Implementing your Analysis. By analysis, I specifically refer to the object
oriented diagrams which are suppose to be language agnostic. Often, teams do
not make this distinction, mainly because they are a Java shop (or pick your
language). As well, working with analysis models is working with abstractions
which is another skill level entirely. Many times analysis is avoided with the
justification that it doesnt produce an executable product. The language and
frameworks are known, so analysis is skipped and we move right into design.
However in many cases analysis is folded right into the design work. The
distinction between analysis and design often gets cloudy; however it is really
a matter of abstractions. Lets see if we can detect the difference.
A common practice for jump starting OO analysis is to look
at the use case or stories for nouns. These nouns will be our first set of
objects, often referred to as Business Objects, because they reflect or often
trace back directly to real business entities. As the set of business objects
grows, we start to try to understand their relationship with each other. While
this is a good exercise in analysis to better understand the relationships of
the business, too often the analysis is what is implemented. The situation is
made easier and more comfortable by the fact that the same type of activity is
conducted by the DBA coming up with initial crack at the schema. However, OO
is about behavior not structure.
Example 1: Its about Behavior
Lets look at a real-world example. At the time I was
working for a genetics research company, there were millions of alleles (just
think of them as strands of DNA) which were produced as an output of one
process. That information needed to be exported from one data store to
another. One of the OO designers of the system was having problems with the
early incarnations of the system because the system was getting bogged down
from a resource and time constraint based on the instantiating millions of
allele objects so that the object could persist itself in the other data
store. This may seem like a simple example but it is at the heart of good OOAD
and at the core of being resilient to change. The allele objects looked great on
paper, they made sense from an academic stand point, but that is not the
behavior of the system. The behavior of the system is to export and import
data. It is to move data from one source to another. Yes, of course, there
must be some validation involved Sure something must validate that the moving
data is consistent, complete and that validation might be done in an Allele
Object or an AlleleValidator. By focusing on behavior, the system becomes
extensible. Given a source and a destination with object maps, the system could
be adapted to import / export proteins or genetic markers. Stealing a quote
from the Matrix, Its the question that drives us and the question is: What
is the likelihood of change? Where is it? Is it the data mapping? Is it the
type of data which is exported? The answer is out there Its looking for you.
Example 2: Inheritance at the right abstraction
Now for you picture people out there a more hypothetical
but illustrative example. Lets concern the development of a University
registration system. Reading through the use cases or stories we see the need
for a student object, however there are different types; full-time students and
part-time students. So, we logically see an abstraction of student, which
would be a super class for the two student types. Looking further, the student
will have courses so we might have a model which looks like the following:
Figure 1 Student Class Diagram Analysis
While often this is referred to as design It is much closer
aligned to analysis. Lets dig deeper:
- What happens if a part-time student becomes a full time
student? How does an object morph from one object type to another?
This is the most significant issue,
the morphing of one type of object to another type. This stems from the
structural inheritance which will be detailed later in this article. An
implementation of this model at run time would leave you with 2 choices for the
transition of a part-time student to a full time student: the existence of 2
objects at one time which represent 1 entity from the real world, or the zero
objects at one point in time. Either approach results in inconsistencies in
the system at one point in time.
- How do we handle the addition of another classification of
This is a fairly simple example and
the likelihood of another classification is unlikely for most systems. However
if you extend this out to other similar situations, or if you can imagine
another classification of a student there are significant issues. The most
significant are identified with morphing; after creating another subclass, all
the morphing code will have to change. The situation would also exacerbate the
next identified issue.
- Can a PartTimeStudent be used any where in the system a Student
can be used? Or will you end up with code which checks for the instance
of a known type?
If there are areas of code which
check for instance types to perform specific functionality, then the design is
not extensible. In short, it doesnt follow the open-close principle which will
be detailed later. Thus, small changes will ripple through large sections of
A More Refined Solution
It must be restated that this relationship makes sense from
an analysis perspective, but dont let it creep into your design. So how did
we get here? A part-time student is a student. It passes the is a
relationship test. It should be good, right? Lets look at how we might
change the design to address the key issues.
Figure 2 Student Class Diagram Design
In this case the student has a student classification, which
in turn has specialized extensions. The beauty here is the relationships make
sense, and there is no morphing issue with the student. In this case a student
classification can change without losing the identity of the student or its
relation with its courses.
Principles of Inheritance
It has been my experience that improper use of inheritance
is the largest contributor to poor object-oriented design. Understanding certain
key principles pertaining to inheritance must be understood in order to develop
good object-oriented systems. Lets start with the reasons to inherit:
- Structural Object 2 has structure which is similar to
- Functional Object 2 has functions which are desired by
- Behavior Object 2 is a specialized version of Object 1.
From a quality of use or best practices stand point these
are listed in the worst reason to best reason to use inheritance. The most
important factor is to know why you are inheriting. By increasing the quality
of the type of inheritance the quality of the design is increased which in many
cases is more resilient to change. Lets discuss each type in further detail.
Structural Inheritance is the least desirable reason to
inherit. The main reason should be obvious. Basically, it provides little to
no benefit outside of code reuse. There is little or no benefit of polymorphic
behavior or encapsulation. In systems with a high degree of structural inheritance,
as a class is extended for its structure, then all the relating classes seem to
need to be extended to accommodate the new structure. This does not agree with
the substitution principle or the open-close principle outlined below. A change
of structure causes a ripple effect across a number of the classes in the code
The difference between functional and behavior is simply the
need for a method contained in an object vs. the extension of a specific type
of object. It seems best illustrated with an example. In this example, we
are building a system where we will need a file path. In analyzing the model
we have to decide between 2 approaches illustrated below.
Figure 3 Inheritance vs. Aggregation Model
Remember Its the question that drives us. So what are
Is FilePath a String?
It will be a String which
represents a filepath. It seems to make sense.
Does FilePath contain a String?
I guess it could.
The more important question is what does a String do? And
what does a FilePath do?
First why is this question more important?
Basically because it defines behavior and object-orient development is all
about behavior. So what is a String? It is an array of chars, which can be
trimmed, we can get index of chars and strings, we can sub-string.
So what is a FilePath? It is a string which represents the
location of a file. What does it do? It can validate if a file exists, it can
give you the drive, the path, the file extension.
It would seem that you would want to use methods of a string
to get provide the functionality of the FilePath. You need to do a sub-string
to provide most of the functionality we want the FilePath to have. However, if
FilePath inherits from a String then any client of FilePath would be able to
sub-string any part of the FilePath, this would break encapsulation. The very
thing FilePath was responsible for is corruptible by inheriting from a String.
Now some of you C++ heads out there are saying well I could inherit privately,
thus protecting the integrity of the FilePath object. While this is true,
later principles will illustrate that this is still poor object design. Thus
aggregation is a better approach for this design and hopefully demonstrates
what is meant by functional inheritance.
Behavior inheritance is inheritance which is truly an
extension of another objects behavior. In the ideal situation an instance of a
subclass can be passed to a method which expects the super class, this known as
Liskov Substitution Principle. Could you image in our previous example passing
an instance of a FilePath to any method which has a String as a parameter? It
seems intuitive at this point that it doesnt make sense.
Barbara Liskov in conjunction with Jeannette Wing presented in 1993 a paper entitled Family Values: A Behavioral
Notion of Subtyping containing the liskov substitution principle. The principle states If for each object o1 of type S there
is an object o2 of type T such that for all programs P defined in terms of T,
the behavior of P is unchanged when o1 is substituted for o2, then S is a
subtype of T. [Liskov88]. WOW! Read that a few times fast Basically
restated; an instance of an extension of a class, should be able to be passed
to a method expecting the base class with no consequence or side effect. This
principle is at the heart of good inheritance. By following this principle
designs begin to be extensible, which brings us to the next principle.
Open / Close Principle
Coined by Bertrand Meyer, and expounded upon by Robert C. Martin,
the open-close principle basically means that units of code, namely classes and
methods, are open for extension and closed for modification. This principle
works congruently with the substitution principle. Applying this principle,
code is closed to modification If change is required the only solution is to
extend. Of course it takes a significant amount of design effort to reach this
level of maturity; the benefits being a significant resilience to change.
Most books on the subject would have us believe OOAD is
about polymorphism, inheritance and encapsulation. While these are cool buzzwords
and powerful tools in the object-oriented arsenal there is more to a good
object-oriented design. OOAD is about behavior. Object-Oriented systems
developed around behavior are more resilient to change.
Understanding the behavior of objects and answering tough
questions like what is the likelihood of change? are critical in designing
applications that will last longer than the first vision.
There are a number of leaders which have helped to form the views expressed in this article which we should pay homage to, in particular the work of Barbara Liskov, Jeannete Wing and Robert C. Martin. They have had a huge impact on shaping the discipline of our industry.
I would also like to personally thank Ron Campbell, the great wordsmith and musician, who made this