|  |
Part I: Why Use OO Technologies?
2011 update: (grin!) I wrote this sometime in the late 1990's. For a
far more up-to-date discussion on this subject, read Robert C.
Martin's book Clean Code. Then read it again. And again.
This article captures my delivery of a module that I taught
when students learned about objects, classes, encapsulation,
inheritance, polymorphism, dynamic binding, overloading,
overriding, etc. I used to teach this after introducing those
OO concepts and by then, the class was ready to riot on me.
When I taught this first, the course would go so much more
smoothly. My experience is that this helps others (especially
people like me with 5+ years of procedural experience) who must
work with object-oriented professionals, are trying to make the
transition to object-oriented technologies and maybe feel as if
they're grinding their gears.
I first encountered people with a true, OO mindset
after 8 years of doing procedurally oriented programming, and
found the transition incredibly painful and confusing until I
saw a side by side demonstration of how development occurs for
a project using non-OO technologies vs OO technologies, and
how these approaches are fundamentally different. It gave me
the motivation that I needed to spend the next 6 months
becoming comfortable with OO, and to finally get on the bandwagon.
So let's get started!
After learning OO concepts, many students invariably ask the
million dollar question:
Why on earth would I ever want to learn this??
They feel OO concepts are alien and more complex than technologies
they currently use. Others feel that most of the concepts are the
same, just repackaged. We still have variables and methods (which
are just a fancy name for functions), except now they're hidden in
a class and it's even harder to access the variables and methods
than before! We already had divide and conquer, except that
instead of functional decomposition, we now have object
decomposition. Many have heard that Java's on the wane, that it
doesn't deliver what was promised.
Let's walk through some examples, and ask again afterwards if
you still feel the same way.
Building a Graphical Tool
Let's say we've gotten requirements to build a tool that renders
different types of shapes, and does things with those shapes.
For example, it may manipulate Circles, Triangles, Squares,
Rectangles, etc. This tool will allow users to draw(), print(),
move(), resize(), hide(), show(), etc.
We will sketch out how you would implement this using procedural
technology, then with object-oriented technology.
Procedural Solution
A typical approach would be to declare variables necessary
to store the information we need for our different shapes,
e.g:
int height;
int width;
int radius;
int shapeType;
|
where shapeType is some enumeration of our shapes, e.g.
Circle = 0, Triangle = 1, Square = 2, Rectangle = 3, etc.
You might even keep track of the color, foreground, background,
etc.
Now let's implement a draw() subroutine that draws each of our
shapes. It might look something like this:
void draw(int shapeType) {
switch(shapeType) {
case CIRCLE:
/* code here to get the radius
and draw a circle */
break;
case TRIANGLE:
/* code here to use a base/height
to draw a triangle */
break;
... (ad nauseum)
default:
/* Error!! Can't draw an unknown shape! */
}
}
|
Great! Let's say we got that done. Now, what would
a print() subroutine look like?
void print(int shapeType) {
switch(shapeType) {
case CIRCLE:
/* code here to print a circle */
break;
case TRIANGLE:
/* code here to print a triangle */
break;
... (ad nauseum)
default:
/* Error!! Can't print an unknown shape! */
}
}
|
And we would do the same for our move(),
resize(), hide(), show(), etc.
Our Main Program
Let's say we want a main program that calls
various subroutines for our various shapes. How
would we do this?
public static void main(String[] args) {
radius = 3;
draw(CIRCLE);
base = 4;
height = 6;
draw(TRIANGLE);
...
print(SQUARE);
}
|
This looks simple enough to build.
Now: what happens if I want to add a new shape: Hexagon?
Non-OO Development/Testing Efforts
What development effort must occur?
- You must add a new shapeType: Hexagon = 6
- You must go to all of your subroutines: draw(), print(), etc.
and add a new case statement for that new shape. Can you see
some problems?
- It's as if someone took a shotgun to your code. In order
to add a new shape, you must touch every function that you
wrote!
- Question: What happens if you add hexagon to 19 of
your 20 subroutines? Well, it turns out, the compiler
won't be able to detect this, and now you're counting on the
developer's diligence during unit testing, or your quality
assurance group during system testing to catch these errors.
- You must change your main program to use this new shape,
and manipulate the appropriate variables
What testing effort must occur?
- You must test all of the new code that was added (and
test Every Single Method)
- The switch statement is an error-prone construct, such
that you must also test all of the old code to make sure you
didn't break anything!
- You must test your main program
As for all of these intertwined global variables floating
around: circles have a radius. Do squares? So what happens if
a subroutine tries to access a variable that wasn't initialized
correctly, or tries to access a variable it's not supposed to
access (e.g. the square's draw() subroutine tries to use the
radius)? Nothing stops us from writing code that does this.
-
How complex is all of this to maintain over time?
-
Can you imagine that code is shipped to production which
is not production-ready?
Object-Oriented Solution
Using objects/classes, inheritance and polymorphism, how
would we build this graphical tool using object-oriented
technologies?
One approach would be the following: first create an abstract
Shape superclass with a bunch of abstract methods such as draw(),
print(), move(), resize(), etc.
public abstract class Shape {
protected abstract void draw();
protected abstract void print();
protected abstract boolean move();
protected abstract boolean resize();
...
}
|
By doing this, we're creating an abstract class that acts
like a business contract. Anyone who wants to create a
subclass must provide an implementation for these methods.
How would we create our actual shapes?
We would create concrete subclasses of Shape such
as Circle, Triangle, Square, Rectangle, etc. These
subclasses override the superclass methods.
public class Circle extends Shape {
public void draw() {
// draw a Circle
}
public void print() {
// print a Circle
}
...
}
public class Triangle extends Shape {
public void draw() {
// draw a Triangle
}
public void print() {
// print a Triangle
}
...
}
|
This contract is very important. If we don't
provide an implementation for all of the abstract
methods, we won't be able to compile the code.
The Main Program
Now let's implement a main() which creates an array of
Shapes, populates them with different shapes, and calls
the draw() method for each shape.
public static void main(String[] args) {
Shape[] shapes = new Shape[10];
shapes[0] = new Square();
shapes[1] = new Triangle();
shapes[2] = new Circle();
...
for (int i = 0; i < shapes.length; i++)
shapes[i].draw();
}
|
Note before we continue: if you find the following
code confusing:
Shape shape = new Circle();
|
please click here to read
an article about how to convert objects from one type to
another, and what the rules are. If you are not confused,
please continue.
It turns out, because of inheritance, polymorphism
and the concept of dynamic binding, we call the same
draw() method, and the correct draw() method for
the correct Shape subclass gets automatically called!
Maintaining an OO Design
Now, let's say we need to add a new shape: Hexagon.
We'll assume we properly encapsulate each class.
What do we need to do?
- Add a new Shape subclass called Hexagon, and implement
the methods for that class
- Question: Now what happens if you implement
19 out of 20 methods, but forget one? Again, since the
Shape class is abstract, concrete subclasses of Shape must
implement all of the abstract methods, otherwise the
code won't compile. This means the developer now
catches and fixes these mistakes (and not the client,
during production).
- Add a line in our main program to instantiate this
new type (but nothing else has to change, since we have
polymorphism and dynamic binding which means the generic
draw() method will now call the correct draw() method for
our new Hexagon subclass!)
What do we need to do to test our new class?
- Test our new Hexagon class
Do we need to test anything else? No!
- We don't need to re-test the other classes, other
than making sure we instantiated our new shape correctly in main.
- We never touched the Shape class or other subclasses.
Problems we normally catch at runtime, during testing, now get
caught at compile time. This is both good and bad; new Java
developers often find this annoying initially. They
try to write a program, and Java barks at this, they change
it, and Java barks at that... But that's because the Java
language is more strongly typed than other languages, and the
compiler is now not just a compiler, but also a mini-quality
assurance department! Object-oriented concepts, when applied
correctly, force developers to adopt better programming practices.
Are you beginning to understand that these heavily intertwined
concepts: objects, classes, encapsulation, inheritance, and
polymorphism, are designed specifically to address each of the
maintenance issues raised? If so, you've become OO aware.
Until you have become OO aware, you cannot even begin to work
towards becoming OO competent, much less OO savvy. I don't care
how hard you work on learning Java syntax, or even understanding
objects, classes, inheritance and polymorphism. If you do not
understand where we are headed and why, from a maintenance
perspective, you will never utilize the full potential that this
language has to offer.
Entry Barrier to OO
Developers with an extensive procedural background may ask: is
it worth trying to make the transition? (And unfortunately, as of
2006 that question is getting harder to answer).
How many times have changing requirements set you back?
Well, guess what? It's not necessarily your users' fault.
The world around us is changing every day. Change is a
part of life. It is something we must learn to accept and manage.
On the other hand, is it necessarily the developer's fault?
Developers often want to do the right thing. So where's the
problem?
The problem is that non object-oriented methodologies are
inherently flawed with respect to building systems that, over the
long term, are robust, maintainable, flexible, extensible, and
scalable.
When I first encountered people with a true OO mindset in PA, I saw
the most incredible thing. A large room of total chaos, with one
exception. There was this little island of sanity in that room, and
I discovered they were five developers with OO backgrounds, with
anywhere from 2 to 10 years of experience. They were grumpy former
Smalltalkers who were asked to learn Java (this was back in March 1997).
Time and time again, a user or manager would come running up to the
group saying: stop the presses! There's been a change in the
requirements! What's the damage? How far is this going to set us back?
The response from this group was always the same:
"It will take us anywhere from 5 minutes to 2 days to fix
this problem". And 90% of the time, it was 5 minutes.
Taking a Step Back
Do you still think that OO is just the same stuff as before,
just repackaged? Do you think that functional decomposition
and object decomposition are essentially the same thing? I
hope not, certainly not from a maintenance standpoint.
If you've been trying to learn OO:
- Does all of this make sense?
- Does this help to alleviate some of the fear, frustration,
maybe even the anger you felt when you first encountered these
strange new concepts?
- Most important of all: does this free you, so that you can
focus on what is truly important? If you've become truly OO aware, now
put your nose to the grindstone learning the syntax and cornerstone OO
concepts. Because if you are OO aware and you work hard, there is a good
chance you will wake up one day and realize: hey, I've made it.
Non-OO and OO mindsets are fundamentally different. Can you imagine
that non-OO developers who first encounter OO technology feel like
someone threw a monkeywrench into their well-oiled machinery? Well,
what do you think OO developers feel when they must work with non-OO
developers? Yes, they feel the same way.
There is nothing more unstoppable than a small SWAT team of experienced
OO developers. Conversely, there is no higher risk of failure
than when you have a team of mixed developers. For OO projects
to succeed, everyone must be on board with respect to
understanding the OO vision. Developers, architects, management.
If they are not, you will fail. For a chance at success, everyone
must attend Java/OO training, and they must attend a class with a
good instructor who's "gotten it". There are still a lot of people out
there who haven't. Which is why most of the industry is in a
crisis, and why project after project turns into a death march.

|  |
|