Object Orientation

Object Orientation vs. Process Orientation

Focusing on objects can help us deal with complexity. Contrast object-orientation with process-orientation.

OBJECTS PROCESSES
Things are fundamental Processes are fundamental
Democritus Heraclitus
Mostly bottom-up; focus on building blocks and components Top-down; focus on inputs and outputs
Object-oriented Algorithmic, or process-oriented
Recognizes fundamental objects and their behavior — objects know how to act Recognizes fundamental processes that "operate on" objects
Modern Traditional (Classical)
Directly addresses complexity through abstraction, classification and hierarchy Has limits to the amount of complexity it can handle: "structured programming appears to fall apart when applications exceed 100,000 lines or so of code." Also, does not directly address data abstraction and information hiding.
Natural view of "system" as a set of highly reusable cooperating objects with crisply defined behaviors; communication via messages Views a system in terms of algorithms; subroutines process data (which is often "global" data); Does not address concurrency well, if at all
OOD leads to systems which are smaller; based on stable intermediate forms; more resilient to change; and can evolve incrementally Does not scale up well. No real components to swap out; changes generally have wide scope

OO Basics

OO Design is bottom-up, not top-down

It is unclear how to design a complex system like an OS, DBMS, robot, or traffic control system in a top down fashion using stepwise refinement because such a system is not a single input / single output program. In such systems it is better to focus on the components, such as buffer managers, query optimizers, transaction managers, etc. Even if the component is essentially a functional component, you still name it with a noun. Examples: ImageMapper, SiteInitializer, StringTokenizer, QueryOptimizer, Enumerator, StreamReader, StreamWriter, Factory, etc.

When you need to implement the various behaviors of objects you apply top-down algorithm design techniques. You just don't do the system top-down.

Roots of Object-Orientation

Vocabulary

Object, Class, Abstraction, Encapsulation, Modularity, Hierarchy, Inheritance, Generalization, Specialization, Superclass, Subclass, Aggregation, by Value, by Reference, Type, Field, Method, Constructor, Message, Polymorphism, Identity, State, Behavior, Use Case, Scenario, Framework, Pattern

Computer Scientists need to...

...but realize that

The Big Picture

An Object Oriented system is one that is designed as a cooperating set of objects, where "code" is simply just another property of an object, namely its behavior

A Process Oriented system is one that is designed as a set of subroutines, each passing around pieces of data to come up with a "result"

Basically, that's it, but the next level of refinement of the terminology requires that the objects be classified — grouped into classes — and that the classes be arranged hierarchically in an inheritance lattice.

Picky, picky

Some people are picky and require that to be called "object-oriented" a system must go even further and directly support something called "late binding" or "dynamic polymorphism" (otherwise, they say, you can only call the system "object based.")

This distinction between object-based and object-oriented was first popularized in the famous paper by Luca Cardelli and Peter Wegner, "On Understanding Types, Data Abstraction, and Polymorphism" ACM Computing Surveys, December 1985.

Philosophical Motivation

What is more fundamental, objects or processes? For example: is there a fundamental process in the universe called push, or are stacks a fundamental kind of object in the universe which happen to behave in such a way that they only grow by adding to the top?

Simple Examples

Java

public class Circle {
    private double radius;
    private double x, y; // center of the circle
    public Circle(double xc, double yc, double r) {x = xc; y = yc; radius = r;}
    public double area() {return Math.PI * radius * radius;}
    public double perimeter() {return 2.0 * Math.PI * radius;}
    public void expand(double factor) {radius = radius * factor;}
    public void move(double dx, double dy) {x += dx; y += dy;}
}

C++

class Circle {
private:
    double radius;
    double x, y;        // center of the circle
public:
    Circle(double xc, double yc, double r): x(xc), y(yc), radius(r) {}
    double area() {return PI * radius * radius;}
    double perimeter() {return 2.0 * PI * radius;}
    void expand(double factor) {radius = radius * factor;}
    void move(double dx, double dy) {x += dx; y += dy;}
};

C#

class Circle {
    private double radius;
    private double x, y;        // center of the circle
    public Circle(double xc, double yc, double r) {x = xc; y = yc; radius = r;}
    public double Area() {return Math.PI * radius * radius;}
    public double Perimeter() {return 2.0 * Math.PI * radius;}
    public void Expand(double factor) {radius = radius * factor;}
    public void Move(int double, double dy) {x += dx; y += dy;}
};

Groovy

class Circle {
    private def x, y, r
    Circle(x = 0, y = 0, r = 1) {this.x = x; this.y = y; this.r = r}
    def area() {Math.PI * r * r}
    def perimeter() {Math.PI * 2.0 * r}
    def expand(factor) {r *= factor}
    def move(dx, dy) {x += dx; y += dy}
}

Ruby

class Circle
  def initialize(x = 0, y = 0, r = 1)
    @x = x
    @y = y
    @r = r
  end
  def area
    Math::PI * @r * @r
  end
  def perimeter
    Math::PI * 2.0 * @r
  end
  def expand!(factor)
    @r *= factor
    self
  end
  def move!(dx, dy)
    @x += dx
    @y += dy
    self
  end
end

Python

import math

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    def area(self):
        return math.pi * (self.radius**2)
    def perimeter(self):
        return math.pi * self.radius * 2
    def expand(self, factor):
        self.radius *= factor
        return self
    def move(self, dx, dy):
        self.x += dx
        self.y += dy
        return self

JavaScript, using prototypes

function Circle(x, y, r) {
    this.x = x || 0;
    this.y = y || 0;
    this.r = r || 1;
}
Circle.prototype.area = function() {
    return Math.PI * this.r * this.r;
}
Circle.prototype.perimeter = function() {
    return Math.Pi * 2.0 * this.r;
}
Circle.prototype.move = function() {
    this.x += dx;
    this.y += dy;
}
Circle.prototype.expand = function(factor) {
    this.r *= factor;
}

How to "read" OO Code

Circle c = new Circle(10, 5, 3.2); // Java
let c be a reference to a newly constructed circle centered at (10, 5) with radius 3.2
Circle c(10, 5, 3.2); // C++
let c be a newly constructed circle centered at (10, 5) with radius 3.2
c.area();
ask the circle what its area is
c.move(3, 2);
tell the circle to move itself 3 units in the x direction and 2 units in the y direction

Extensibility of Object-Oriented Systems

One of the major advantages of OOP is that it often becomes possible to extend a system by adding new types in a hierarchy without changing existing source code. (Of course, it ain't so easy to add new operations!)

Classes or Prototypes?

Where do the objects come from? There are two main styles of OOP:

Case Study: A Shape Library

Consider the task of building a library of Shapes that can be used in draw programs, math tutorial programs, CAD programs, etc.

Let's look two ways to write the library

  1. In a non-object oriented fashion in C
  2. In an object-oriented fashion in Java
Comparing the two libraries makes clear the essential distinction between what is object-oriented and what is not.

The Shape Library in Non-Object-Oriented C

/*****************************************************************************
*
*  shapelibrary.c
*
*  Illustration of a hierarchy of shapes, implemented in the non object
*  oriented language C.  This program shows that C is insecure when it
*  comes to implementing hierarchies.
*
*****************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>

/* Some C compilers do not define PI for you. */

#define PI (acos(-1.0))

/*
 * The usual implementation of a "class hierarchy" in C is to define a
 * struct for the root type which includes a "tag" member of an
 * enumerated type.
 */

typedef enum {circle, square, polygon} shape;

/*
 * The root class of the hierarchy is called Figure.  Every figure
 * has a color, but then there are different kinds of figures so we
 * keep a tag to distinguish them.  Then we have a set of attributes
 * which supposedly depend on that tag, grouped into a union.  In C,
 * you cannot "force" dependency on the tag: e.g. for a Figure f you
 * are completely free to set f.kind = circle and then immediately
 * assign f.a.side = 5.2, and no compile-time or run-time error will
 * ever be generated!
 */

typedef struct {
  int color;
  shape kind;                                 /* what kind of figure is it? */
  union {                                     /* figure-specific attributes */
    float radius;                             /*   radius for circles       */
    float side;                               /*   side length for squares  */
    struct {float *x; float *y; int n;} p;    /*   point array for polygons */
  } a;
} Figure;

/*
 * Now all operations on figures have an embedded switch, controlled by
 * the tag of the figure.  Note that one drawback of this is that if
 * we ever extend the system by adding new shapes, the bodies of all
 * of the operations need to be modified.
 */

float perimeter(Figure f) {
  switch (f.kind) {
    case circle: return 2.0 * PI * f.a.radius;
    case square: return 4.0 * f.a.side;
    case polygon: {
      double result = 0.0;
      int i;
	  for (i = 0; i < f.a.p.n; i++) {
	     float deltaX = f.a.p.x[i + 1] - f.a.p.x[i];
	     float deltaY = f.a.p.y[i + 1] - f.a.p.y[i];
	     result += sqrt(deltaX * deltaX + deltaY * deltaY);
	  }
      return result;
    }
  }
}

float area(Figure f) {
  switch (f.kind) {
    case circle: return PI * f.a.radius * f.a.radius;
    case square: return f.a.side * f.a.side;
    case polygon: {
      double result = 0.0;
      int i;
      for (i = 0; i < f.a.p.n; i++) {
        result += f.a.p.x[i] * f.a.p.y[i + 1] - f.a.p.x[i + 1] * f.a.p.y[i];
      }
      return result / 2.0;
    }
  }
}

/*
 * Here are the operations which do not depend of the kind of shape
 * we have.
 */

int getColor(Figure f) {
  return f.color;
}

void setColor(Figure f, int color) {
  f.color = color;
}

/*
 * To fake a constructor in C, you write a function that returns a
 * particular kind of Figure.
 */

Figure makeCircle(int color, float radius) {
  Figure f;
  f.color = color;
  f.kind = circle;
  f.a.radius = radius;
  return f;
}

Figure makeSquare(int color, float side) {
  Figure f;
  f.color = color;
  f.kind = square;
  f.a.side = side;
  return f;
}

Figure makePolygon(int color, float packedCoordinates[], int numPoints) {
  Figure f;
  int i;
  f.color = color;
  f.kind = polygon;
  /*
   * Copies the packed array of coordinates into separate x and y arrays
   * and adds a new last point which is the same as the first point
   * to allow cleaner implementations of perimeter and area.
   */
  f.a.p.n = numPoints + 1;
  f.a.p.x = malloc(sizeof(float) * (f.a.p.n + 1));
  f.a.p.y = malloc(sizeof(float) * (f.a.p.n + 1));
  for (i = 0; i < numPoints; i++) {
      f.a.p.x[i] = packedCoordinates[i * 2];
      f.a.p.y[i] = packedCoordinates[i * 2 + 1];
  }
  /* Make the last point identical to the first */
  f.a.p.x[f.a.p.n] = f.a.p.x[0];
  f.a.p.y[f.a.p.n] = f.a.p.y[0];
  return f;
}

/*
 * The main program makes some figures then calls some operations
 * on them.
 */

void show(Figure f) {
  setColor(f, 0x00FF33CC);
  printf("Perimeter = %f, area = %f\n", perimeter(f), area(f));
}

float aTriangle[] = {0, 0, 5, 0, 0, 12};
float aRectangle[] = {0, 0, 5, 0, 5, 20, 0, 20};

int main() {
  int i;
  Figure fig[5];
  fig[0] = makePolygon(0x0000FF00, aTriangle, 3);
  fig[1] = makeCircle(0x00FF8888, 1.0);
  fig[2] = makeCircle(0x00CCCCCC, 10.0);
  fig[3] = makeSquare(0x00ABCD12, 3.0);
  fig[4] = makePolygon(0x0000FF00, aRectangle, 4);
  for (i = 0; i < sizeof fig / sizeof(Figure); i++) {
    show(fig[i]);
  }
  return 0;
}

This C program

Unions basically suck. It is interesting to note that some languages do allow the construction of a type-safe version of a union. Ada calls this a variant record.

Exercise: Know Ada? If so, port the above C program to Ada.

The Shape Library in Java

The Java version is broken up into several files. First an abstract base class:

package edu.lmu.cs.oop.figures;

import java.awt.Color;

/**
 * A little library of figures.  It is the classic example of object
 * oriented programming.  In our library of figures all figures are
 * derived from an abstract class called Figure.  It is abstract
 * because there are no plain figures; all real figures are instances
 * of classes derived from Figure.  A figure has a color which can be
 * read and written with getColor() and setColor(); the default color
 * is blue.  Also, you can get the area and perimiter of any figure;
 * these methods are abstract because their implementation depends
 * on the particular subclass.
 */
public abstract class Figure {
    protected Color color = Color.blue;
    public void setColor(Color c) {color = c;}
    public Color getColor() {return color;}
    public abstract double perimeter();
    public abstract double area();
}

Now here are three subclasses.

package edu.lmu.cs.oop.figures;

import java.awt.Point;

/**
 * A simple circle class which is part of our figures package.
 */
public class Circle extends Figure {
    protected Point center;
    protected double radius;
    public Circle(Point c, double r) {center = c; radius = r;}
    public double perimeter() {return 2.0 * Math.PI * radius;}
    public double area() {return Math.PI * radius * radius;}
    public void expand(double scaleFactor) {radius *= scaleFactor;}
}

package edu.lmu.cs.oop.figures;

import java.awt.Point;

/**
 * A simple square class which is part of our figures package.
 */
public class Square extends Figure {
    protected Point upperLeft;
    protected double sideLength;

    public Square(Point upperLeft, double sideLength) {
        this.upperLeft = upperLeft;
        this.sideLength = sideLength;
    }

    public double perimeter() {
        return 4 * sideLength;
    }

    public double area() {
        return sideLength * sideLength;
    }
}

package edu.lmu.cs.oop.figures;

import java.awt.Point;

/**
 * A simple polygon class which is part of our figures package.
 */
public class Polygon extends Figure {
    protected Point[] vertices;

    /**
     * Accept any array of points, but make a new array with the
     * first point repeated at the end to store internally.
     */
    public Polygon(Point[] vertices) {
        this.vertices = new Point[vertices.length + 1];
        System.arraycopy(vertices, 0, this.vertices, 0, vertices.length);
        this.vertices[this.vertices.length - 1] = this.vertices[0];
    }

    public double perimeter() {
        double result = 0.0;
        for (int i = 0; i < vertices.length; i++) {
            double deltaX = vertices[i + 1].x - vertices[i].x;
            double deltaY = vertices[i + 1].y - vertices[i].y;
            result += Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        }
        return result;
    }

    public double area() {
        double result = 0.0;
        for (int i = 0; i < vertices.length; i++) {
            result +=
                vertices[i].x * vertices[i + 1].y
                - vertices[i + 1].x * vertices[i].y;
        }
        return result / 2.0;
    }
}

Note:

Exercise: What exactly is so good about source code not compiling when an enhancement is missed?

Design

You need to pay careful attention to knowing which methods should be abstract and which should not. If the implementation is the same across classes (e.g. getColor and setColor) the method should not be abstract; if the implementation is different, that is, it depends on the subclass (e.g. perimeter and area), then it should be made abstract.

Summary

The essential difference between process and object orientation is illustrated here:

oovspo.gif

If you're going to be adding more objects, the OO arrangement is good. If you're going to be adding more operations, the procedural arrangement is good.