Part I: Why Use OO Technologies?

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?

  1. You must add a new shapeType: Hexagon = 6

  2. 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.

  3. You must change your main program to use this new shape, and manipulate the appropriate variables

What testing effort must occur?

  1. You must test all of the new code that was added (and test Every Single Method)

  2. 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!

  3. 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?

  1. 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).

  2. 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?

  1. 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:

  1. Does all of this make sense?

  2. Does this help to alleviate some of the fear, frustration, maybe even the anger you felt when you first encountered these strange new concepts?

  3. 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.