Object Orientation

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

Two Philosophical World Views

Look at the world—or the universe—around you. What do you see? Do you see:

If you see or feel the latter, you may like object-orientation (OO); if you like the former, you may like process-orientation. The latter view sees strings as things that can be uppercased, trimmed, and searched. The former sees uppercasing as a fundamental process in the universe, that uppercases all the things. How it uppercases a paper plate I do not know....

What does this have to do with programming?

OBJECTS PROCESSES
Things are fundamental Processes are fundamental
Democritus Heraclitus
Mostly bottom-up, focused on building blocks and components Top-down, focused 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
Been around since the late 1960s Been around since the dawn of computing
Directly addresses complexity through abstraction, classification and hierarchy Approaches complexity by getting data abstraction and information hiding through clever tricks with functions, like making closures, which start to look like objects.
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 (sometimes global). Subroutines pass data to each other. Subroutines can be composed.
Object-based designs leads to systems which are smaller, based on stable intermediate forms, more resilient to change, and can evolve incrementally (observation by Grady Booch). Appears to not scale up well. No real components to swap out; changes generally have wide scope. People end up packaging functions into modules or packages, which feel like...objects.

These two world views translate into different ways of organizing code. Suppose we needed to compute the areas and perimeters of circles and rectangles. We need four operations:

 CircleRectangle
AreaArea of
Circle
Area of
Rectangle
PerimeterPerimeter of
Circle
Perimeter of
Rectangle

The two world views show different emphases. In the non-OO style, the functions are emphasized. In the OO style, the objects (actually the objects’ types) are emphasized:

Non OO-StyleOO-Style
  • area
    • of circle
    • of rectangle
  • perimeter
    • of circle
    • of rectangle
  • Circle
    • area
    • perimeter
  • Rectangle
    • area
    • perimeter

OO in Practice

If you started learning about “object oriented” in the 1980s, you’d hear about how 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. So today, people say:

Basically, that’s it, but the next level of refinement of the terminology requires that the objects be class-ified — that is, 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. This has led to all discussions of object-orientation being filled with terms such as: 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

But wait! What did the term originally mean?

Exercise: Read each of those.

So the term “OOP” in the words of Alan Kay, was coined to describe “messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.”

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 originally mean the Java or C++ style of inheritance and method overriding we have today.

inigo-oop.jpg

You should understand all the meanings. Kay’s original meaning was influenced by a big idea (attributed to his teacher Bob Barton): that the key to successful decomposition of a system is to break it down into parts that are just like the whole. Kay saw that computers need not be decomposed into separate and barely overlapping concepts of algorithms and data structures, but rather neatly into little computers that communicate through messaging each other.

Classes or Prototypes?

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

Exercise: Recall (or research) why classes are called “classes.”

Examples of Classes

A Java example of a mutable circle class:

public class Circle {
    private double x, y, radius;
    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 x, y, radius;
    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 x, y, radius;
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
  def perimeter = Math::PI * 2.0 * @r
  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

Examples of Prototypes

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

function Circle(x = 0, y = 0, r = 0) {
  this.x = x
  this.y = y
  this.r = r
}
Object.assign(Circle.prototype, {
  area() { return Math.PI * this.r * this.r },
  perimeter() { return Math.PI * 2.0 * this.r },
  move(dx, dy) { this.x += dx; this.y += dy },
  expand(factor) { this.r *= factor }
})
Exercise: Implement circle types in Io and Lua.

How to read OO Code

There’s thing that sometimes pops up in discussions about OO called “Tell don’t ask”. Basically it means that objects should do things, even if one of the things they do is answer queries about themselves. This leads to a way of reading 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 tell the circle to respond with 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

From our discussion so far, we may note:

Let’s see why.

Case Study: A Shape Library

Let’s build a library of Shapes that can be used in draw programs, math tutorial programs, CAD programs, and so on. We’ll make procedure oriented versions and object oriented ones.

Procedure Oriented Implementation

Starting with C:

shapelibrary.c
/*
 *  shapelibrary.c
 *
 *  Illustration of a hierarchy of shapes, implemented in a procedure-oriented
 *  fashion.
 */

#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.side = 5.2, and no compile-time or run-time error will
 * ever be generated!
 */

typedef struct {
    int color;
    shape kind;
    union {
        /* Type-specific attributes */
        float radius;
        float side;
        struct {float *x; float *y; int n;} p;
    };
} 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.radius;
        case square: return 4.0 * f.side;
        case polygon: {
            double sum = 0.0;
            for (int i = 0; i < f.p.n; i++) {
                float deltaX = f.p.x[i + 1] - f.p.x[i];
                float deltaY = f.p.y[i + 1] - f.p.y[i];
                sum += hypot(deltaX, deltaY);
            }
            return sum;
        }
    }
}

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

/*
 * "Constructors"
 */

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

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

Figure make_polygon(int color, float coordinates[], int num_points) {
    Figure f = {.color = color, .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.p.n = num_points + 1;
    f.p.x = malloc(sizeof(float) * (f.p.n + 1));
    f.p.y = malloc(sizeof(float) * (f.p.n + 1));
    for (int i = 0; i < num_points; i++) {
        f.p.x[i] = coordinates[i * 2];
        f.p.y[i] = coordinates[i * 2 + 1];
    }
    /* Make the last point identical to the first */
    f.p.x[f.p.n] = f.p.x[0];
    f.p.y[f.p.n] = f.p.y[0];
    return f;
}

/*
 * Makes some figures then calls some operations on them.
 */

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

int main() {
    Figure fig[5] = {
        make_polygon(0x0000FF00, (float[]){0, 0, 5, 0, 0, 12}, 3),
        make_circle(0x00FF8888, 1.0),
        make_circle(0x00CCCCCC, 10.0),
        make_square(0x00ABCD12, 3.0),
        make_polygon(0x0000FF00, (float[]){0, 0, 5, 0, 5, 20, 0, 20}, 4)
    };
    for (int i = 0; i < sizeof fig / sizeof(Figure); i++) {
        show(fig[i]);
    }
    return 0;
}

This C program:

The lack of safety in C unions is just awful (but remember, by design, C is low-level and simple, rather than secure). However, most other languages have fixed that. Here is the same code in the procedural style, implemented in Swift, using Enumerations:

 Missing content
Exercise: Port this program to other languages with type-safe sum types, for example Ada (with variant records).

Object Oriented Implementation

Let’s start with Swift:

 Missing content

Pretty nice! Now our code is:

For fun, here’s the Java version, broken up into several files. First an abstract base class:

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

Exercise: Port this code to Kotlin and C++.
Exercise: Port to JavaScript and Python, and other dynamically typed languages. Does it feel any different to specify hierarchies with dynamic typing?

The Expression Problem

We haven’t covered very much so far, but this comparison sure stands out:

 Non-OOOO
ExpressionFunctions-first:
  area(c)
Objects-first:
  c.area()
Ease of
adding new
types
Hard because we have to change existing code, adding new branches to the if-statements in all the functionsEasy because no existing code needs to be touched
Ease of
adding new
operations
Easy because no existing code needs to be touchedHard because we have to change existing code, adding new methods to existing classes

It seems like no matter which approach above we take, we will either have to edit existing code when adding operations, or edit existing code when adding types. Designing a programming language or paradigm in which you can add both new types and new operations without changing existing code is called the Expression Problem. Actually, some languages do have nice ways to handle this; In Clojure, for example, you can use protocols to extend systems simultaneously in both directions.

You can read about the Expression Problem, and various approaches to solving it, at Wikipedia.

Getters and Setters

There’s one last thing to cover in this painfully short overview of OO.

In some treatises on “Object Oriented Programming” you will see people say “always make your fields private and access them and update them with methods.” This is not a bad idea, but when the only thing these methods do is access and update, you end up with silly sounding method names like getColor and setColor. We’ve created methods, it seems, just to avoid direct access to a property. So what the heck did with bother for?

Shouldn’t we avoid getters and setters?

What do you think now?

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

Summary

We’ve covered:

  • The Object and Process Philosophies
  • OO in Practice
  • Classes vs Prototypes
  • How to “read” OO code
  • Examples both with and without OO
  • The Expression Problem
  • Getters and setters—evil or not?