Object Orientation

Object orientation, or “OO” is a powerful idea that is still, many years after becoming mainstream computer science, sometimes misunderstood.

Object Orientation vs. Process Orientation

Focusing on objects can help us deal with complexity. Why might this be? 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

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?

Our brains seem to be wired for this, no?

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, and so on (but don’t overdo this).

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

Object oriented principles came out of work in many areas:

Vocabulary

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

OO Design and Programming

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.

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.

What did the term originally mean?

There is great value in understanding the original big idea, but also be pragmatic and understand that language evolves, and for better or for worse, the term means many things to many people. It certainly did not orginially mean the Java or C++ style we have today.

Classes or Prototypes?

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

A Java example of a mutable circle class:

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;}
}

A C# example of a mutable circle class:

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;}
};

A C++ example of a mutable circle class:

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;}
};

A Groovy example of a mutable circle class:

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}
}

A Ruby example of a mutable circle class:

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

A Python example of a mutable circle class:

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

A JavaScript example of a constructor and prototype for mutable circles:

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 (dx, dy) {
    this.x += dx;
    this.y += dy;
}
Circle.prototype.expand = function (factor) {
    this.r *= factor;
}
Exercise: Implement circle types in Io and Lua.

How to read OO Code


Circle c = new Circle(10, 5, 3.2); in Java, means, let c be a reference to a newly constructed circle centered at (10, 5) with radius 3.2.


Circle c = Circle(10, 5, 3.2); in C++, means, let c be a newly constructed circle centered at (10, 5) with radius 3.2.

c.area(); means ask the circle what its area is.

c.move(3, 2); means tell the circle to move itself 3 units in the x direction and 2 units in the y direction.

Note the difference between the functional approach, (move(c, 3, 2)) where you, the boss, move the (inanimate) circle and your code operates on the circle; and the object approach (c.move(3, 2)) where the circle is a living breathing agent that moves itself. Here you can identify with the object; the circle has feelings; it is alive; it moves.

Exercise: Read about what Turkle and Papert have to say about this.

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’s so easy to add new operations!)

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
/*****************************************************************************
*
*  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: Which other languages do you know that allow safe sum/union types?

The Shape Library in Java

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

Figure.java
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.

Circle.java
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;}
}

Square.java
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;
    }
}

Polygon.java
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?

Two dimensions of extensibility

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.

In Clojure, you can use both datatypes and protocols to extend systems simultaneously in both directions. You can do this in other languages, too. How to do it well is known as the Expression Problem.

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.

BUT WAIT! Why do we even have getColor and setColor. Shouldn’t we avoid getters and setters?

Exercise: Prepare and give a 10 minute TED-style talk on the evils of getters and setters.