20. OOP: Object-oriented Programming

In these notes you will learn:

  • The idea of Objects in programming.
  • How to create simple objects.
  • How to add functionality to an object.

20.1. Introduction

Object-oriented programming, or OOP for short, is a popular style of programming supported by most programming languages.

The idea is that a program deals with many objects that share common properties and do similar things. For example, we may wish to animate 3 balls bouncing on the screen, each with it’s own fill colour, size, and speed. Instead of writing the code for 3 balls in our program:

// ball 1
float ball1X, ball1Y, ball1Dx, ball1Dy, ball1Diam;
color ball1Color = color(0, 255, 0);

float ball2X, ball2Y, ball2Dx, ball2Dy, ball2Diam;
color ball2Color = color(0, 255, 0);

float ball3X, ball3Y, ball3Dx, ball3Dy, ball3Diam;
color ball3Color = color(0, 255, 0);

and then writing the code to manage all of those balls bouncing around (remember the if-statement for detecting edges?), we can notice that off of these balls have quite a lot in common, abstractly: they all have a colour, a size, a location on the screen, and they move at a certain speed and direction. Further, each ball needs to worry about not falling off of the screen.

Really, the only difference between the balls is in the values to all of the above properties. So we need a way to describe, in general terms, what kind of behaviour we want from a ball. We’ll call this an object, and make 50 copies of it, each with slightly differing values!

20.2. Ball Objects

Let’s start by converting a familiar program into OOP. Say we want to draw a ball on the screen at some (x, y) location. Here’s how we’ve been doing it so far:

void setup() {
    size(500, 500);
}

void draw() {
    background(255);

    fill(255, 0, 0);
    noStroke();
    ellipse(250, 250, 150, 150);
}

This style of programming is sometimes called procedural programming, and it emphasizes the statements used for displaying the circle.

OOP breaks this process down into two broad steps: First decide what information a ball needs to have in order to be displayed, then deal with aactually displaying it.

So what do we want a ball to be? At the very least a ball should have these attributes:

  • (x, y) values for the ball’s centre.
  • A diameter.

To tell Processing what we think a ball is, we write the following class definition:

class Ball {

    // A ball has an (x, y) position
    float x;
    float y;

    // And a diameter
    float diam;
}

A class is a collection of all of the variables (and possibly functions) that an object can contain. The class above tells Processing that objects of type Ball contain three float variables: x, y, and diam.

Now we can change our original program to use this new class definition:

class Ball {

    // A ball has an (x, y) position
    float x;
    float y;

    // And a diameter
    float diam;
}

Ball b;

void setup() {
    size(500, 500);
    b = new Ball();
    b.x = 250;
    b.y= 250;
    b.diameter = 150;
}

void draw() {
    background(255);
    fill(255, 0, 0);
    noStroke();
    ellipse(b.x, b.y, b.diameter, b.diameter);
}

Note

Obviously, this is more work than drawing a single ball! But as you will soon see, this will become handy in a bit, as our program increases in size and complexity.

Lets look at this program in detail. To create a Ball we need two things:

  1. A variable of type Ball. We declared that we want one right before the setup() program:

    Ball b;
    
  2. An object for the variable b to hold:

    b = new Ball();
    

    The expression new Ball() creates a brand new Ball object. In turn, every ball object is guaranteed to contain its own personal copy of the three variaables listen in the Ball class.

Variables in an object are accessed using dot notation. For example:

b.x = 250;
b.y = 250;
b.diam = 150;

The variable b which refers to an object of type Ball, has properties x, y and diam, and so we read b.x, b.y, and b.diam as the x, y, and diam values of the Ball object held by variable b.

20.3. Constructors

A problem with our first version of the OOP program is that it needs a lot of code just to initialize the object

b = new Ball();
b.x = 250;
b.y = 250;
b.diam = 150;

To shorten and simplify initialization, Processing provides special functions called constructors.

Lets add a constructor to our Ball class:

class Ball {
    // a ball has an (x, y) position
    float x;
    float y;

    // and a diameter
    float diam;

    Ball(float initX, float initY, float initDiam) {
        x = initX;
        y = initY;
        diam = initDiam;
    }
}

Ball b;
void setup() {
    size(500, 500);
    smooth();
    b = new Ball(250, 250, 150);
}

void draw() {
    background(255);

    fill(255, 0, 0);
    noStroke();
    ellipse(b.x, b.y, b.diam, b.diam)
}

Now we can create and initialize the ball in a single statement:

b = new Ball(250, 250, 150);

This code is certainly shorter than what we had before!

The constructor is a special kind of a function that follows a couple of extra rules that you need to know about:

  • A constructor’s name is always exactly the same as the name of the class it is in.
  • A constructor has no return type (not even void).

Constructors typically initialize an object’s variables. They could also be used to do other things though. For example, checking that the value of a variable is within an expected range. For example, this constructor checks that the diameter of the ball is positive.

Ball(float initX, float initY, float initDiam) {
    x = initX;
    y = initY;
    if (initDiam <= 0) {
        println("Error: diameter of ball must always be positive!");
        exit(): // immediately quit the program
    }
}

Having constructors to validate data like this is very useful: it’s now impossible for a program to create a ball with a non-positive diameter. If you were to try, you would get an error message, and the program would stop.

Note

Of course, nothing stops a programmer from later changing diameters value to, say, - 73. There are techniques that can handle even that problem.

20.4. A Ball that Draws Itself

Remember that what we’d like to achieve is a separation between the logic for dealing with the ball, and the rest of the logic in the program. Since the Ball object no contains all of the information it needs to have about the ball’s position and size, all it needs to know now is what colour to use, and the ability to draw itself:

class Ball {

    // a ball has an (x, y) position
    float x;
    float y;

    // and a diameter
    float diam;

    // and a color
    color myColor;

    Ball(float initX, float initY, float initDiam, color initColor) {
        x = initX;
        y = initY;
        diam = initDiam;
        myColor = initColor;
    }

    void render() {
        fill(myColor);
        noStroke();
        ellipse(x, y, diam, diam);
    }
}

Here we’ve added two things. First, the Ball object now has a variable that keeps track of the colour of the ball. We’ve changed the ball’s constructor accordingly, to take in a value for color.

Second, we’ve added a render() function to Ball. Notice that this function is almost identical to the code in draw(). The only difference is that we don’t write b.x, b.y and b.diam, but instead simply write x, y, and diam inside the call to ellipse(). That’s because all of the functions inside the class have direct access to any variables declared in the class.

Note

Some programmers name the function that draws the ball draw() instead of render(). That’s fine, but here we will call it render() so as to avoid confusion with the draw() function in the main program.

With this change, the main part of our program becomes short and simple:

// ... Ball class as defined above ...

Ball b;

void setup() {
    size(500, 500);

    b = new Ball(250, 250, 150, color(255, 0, 0));
}

void draw() {
    background(255);

    b.render();
}

20.5. A Ball that Animates Itself

Having converted our original program into an OOP program, let’s now add animation to this program. The Ball object will be responsible for keeping track of its speed and direction, as well as for making sure it doesn’t fall of the edges of the screen. For simplicity, a ball will always have it’s dx and dy values set to 0 by default:

class Ball {

    // a ball has an (x, y) position
    float x;
    float y;

    // and a diameter
    float diam;

    // and a color
    color myColor;

    // and speed variables
    float dx, dy;

    Ball(float initX, float initY, float initDiam, color initColor) {
        x = initX;
        y = initY;
        diam = initDiam;
        myColor = initColor;
    }

    void render() {
        fill(myColor);
        noStroke();
        ellipse(x, y, diam, diam);

        x += dx;
        y += dy;

        // hit top?
        if (y - diam / 2 <= 0) {
            y = diam / 2;
            dy = -dy;
        }

        // hit bottom?
        if (y + diam / 2 >= height - 1) {
            y = height - 1 - diam / 2;
            dy = -dy;
        }

        // hit left?
        if (x - diam / 2 <= 0) {
            x = diam / 2;
            dx = -dx;
        }

        // hit right?
        if (x + diam / 2 >= width - 1) {
            x = width - 1 - diam / 2;
            dx = -dx;
        }

    }
}

Now that we have a (largish) description of a ball, let’s see how to use it to our advantage.

20.6. Animating Three Balls Independently

Given the class definition above, getting three balls to appear on the screen, and applying gravity to them becomes much easier:

//.. definition of class Ball goes here

color red = color(255, 0, 0);
color green = color(0, 255, 0);
color blue = color(0, 0, 255);

Ball b1, b2, b3;

float gravity;

void setup() {
    size(500, 500);
    b1 = new Ball(40, 40, 100, red);
    b2 = new Ball(200, 200, 100, green);
    b3 = new Ball(300, 300, 100, blue);

    gravity = 0.1;
}

void draw() {
    background(255);

    // draw the balls
    b1.render();
    b2.render();
    b3.render();

    // apply gravity
    b1.dy += gravity;
    b2.dy += gravity;
    b3.dy += gravity;
}

If we were to write this program without the Ball object, we would have to put in a lot more work into our draw method!

20.7. Box Objects

To further illustrate when OOP is useful, let’s create another simple object: a rectangular box. We start with the definition of a class Box:

class Box {
    // ...

}

A box should have at least these attributes:

  • An (x, y) position. We’ll decide that (x, y) is the box’s centre.
  • A width and a height.
  • A fill-colour (for simplicity, we ignore the stroke colour).

Each of these attributes is represented by a variable in our class:

class Box {
    float x, y;
    float boxWidth, boxHeight;
    color fillColor;
}

Now we should create a constructor to initialized the variables:

class Box {
    float x, y;
    float boxWidth, boxHeight;
    color fillColor;

    Box(float initX, float initY, float initW, float initH, color initCol) {
        x = initX;
        y = initY;
        boxWidth = initW;
        boxHeight = initH;
        fillColor = initCol;
    }
}

Recall that a constructor is a special kind of function that always has the same name as the class, and has no return type (not even void).

As with Ball, let’s add a render function:

class Box {
    float x, y;
    float boxWidth, boxHeight;
    color fillColor;

    Box(float initX, float initY, float initW, float initH, color initCol) {
        x = initX;
        y = initY;
        boxWidth = initW;
        boxHeight = initH;
        color = initCol;
    }

    void render() {
        rectMode(CENTER);
        fill(fillColor);
        noStroke();
        rect(x, y, boxWidth, boxHeight);

        rectMode(CORNER); // it's the polite thing to do
    }
}

Now we can use this class to write programs like this one:

Box b;

void setup() {
    size(500, 500);

    b = new Box(10, 10, 20, 120, color(255, 0, 0));
}

void draw() {
    background(255);

    b.render();
    b.x = mouseX;
    b.y = mouseY;
}

20.8. Rotating the Box

Let’s make one more change to the Box class: we’re going to allow boxes to be rotated around their centre point.

We will need a variable to keep track of the box’s current angle. Let’s call that variable angle, and add it to the Box class:

class Box {
    float boxWidth, boxHeight;
    color fillColor;
    float angle;

    Box(float initX, float initY, float initW, float initH, color initCol) {
        x = initX;
        y = initY;
        boxWidth = initW;
        boxHeight = initH;
        fillColor = initCol;
        angle = 0;
    }

    void render() {
        rectMode(CENTER);
        fill(fillColor);
        noStroke();
        rect(x, y, boxWidth, boxHeight);

        rectMode(CORNER);
    }
}

Initially, angle is 0, i.e. the box is not rotated at all. Notice that we do not include angle as an input parameter in the constructor. There’s no deep reason for that: it just seems that for many uses a starting angle of 0 is useful, and so there’s no need to require the user to specify that every time they create a box.

Now that we have angle in place, we need to modify render() to draw the box using it. As discussed in the notes on translation and rotation, this involved using:

  • pushMatrix to save the state of the screen’s coordinate system before we rotate the box.
  • translate to move the origin to the centre of the box.
  • rotate to apply the rotation to the box.
  • popMatrix to restore the coordinate system to the state is was when we called pushMatrix.

Here is the modified render() function:

void render() {
    rectMode(CENTER);
    fill(fillColor);
    noStroke();

    // save current coordinate system
    pushMatrix();

        // move the origin to the centre of the box
        translate(x, y);

        // rotate coordinate system 'angle' degrees
        rotate(radians(angle));

        //draw the rectangle
        rect(0, 0, boxWidth, boxHeight);

    popMatrix();

    rectMode(CORNER);
}

Now that boxes can rotate, we can revisit a program that we wrote eariler, to rotate two boxes:

Box a, b;

void setup() {
    size(500, 500);

    a = new Box(100, 100, 20, 120, color(255, 0, 0));
    b = new Box(350, 350, 50, 100, color(0, 255, 0));
}

void draw() {
    background(255);
    a.render();
    a.angle += 1;

    b.render();
    b.angle += -1.75;
}

Compare this with the techniques we were using without object oriented code. This is much simpler, because we noticed that rotating boxes all have common properties and behaviour. This is the power of OOP.

20.9. Adding Some Randomness

A fun way to make Ball objects is to choose their size, position, velocity, and colour at random.

To get random numbers, we’ll use the built-in Processing function random(lo, hi). This function returns a value between lo and hi, that is chosen (uniformly) at random. In other words, any number between lo (inclusive) and hi (exclusive) is equally likely to be returned.

Note that hi is never returned by random(lo, hi), but that lo might be.

Now we can use this to draw a ball with random values. Note that in our original definition of the Ball class, we defined a constructor that takes as parameters the ball’s (x, y) position, it’s diameter, and colour. It turns out that we can add as many constructors as we like to suit our needs, as long as differ by their list of parameters. For this example, let’s add a constructor that takes no parameters and sets all of the ball’s attributes randomly:

class Ball {

    // ... variables

    // Our first constructor
    Ball(float initX, float initY, float initDiam, color initColor) {
        x = initX;
        y = initY;
        diam = initDiam;
        myColor = initColor;
    }

    // Build a ball randomly
    Ball() {

        // We'll insist that the ball starts close to the center of the
        // screen.
        x = random(200, 300);
        y = random(200, 300);

        diam = random(25, 75);

        dx = random(-2.0, 2.0);
        dy = random(-2.0, 2.0);

        // random color
        float rred = random(0, 256);
        float rgreen = random(0, 256);
        float rblue = random(0, 256);

        ballColor = color(rred, rblue, rgreen);
    }
}

Note the following:

  • The constructor Ball() has the same name as the constructor that we have already defined. That’s ok because they don’t have the same parameters. This allows processing to distinguish between the two.

  • We set our ball’s (x, y) position to be some randomly selected number between 200 and 300.

  • The diameter is chosen to be some number between 25 and 75.

  • dx and dy are chosen to be between -2.0 and 2.0 both.

  • For ballColor, we must choose three random values, for red, green, and blue. The four statements we used for ballColor could have been rewritten as:

    ballColor = color(random(0, 256), random(0, 256), random(0, 256));
    

Now it’s easy to create random balls:

//... Ball class code above
Ball b1, b2, b3;

float gravity;

void setup() {
    size(500, 500);
    b1 = new Ball(); // random ball 1
    b2 = new Ball(); // random ball 2
    b3 = new Ball(); // random ball 3
}

void draw() {
    background(255);

    // draw the balls
    b1.render();
    b2.render();
    b3.render();

    // apply gravity
    b1.dy += gravity;
    b2.dy += gravity;
    b3.dy += gravity;
}

20.10. Choosing Colours Randomly

Creating colours randomly is something you might want to do in many different programs, so it is useful to create a special function just for that:

color randomColor() {
    float r = random(0, 255);
    float g = random(0, 255);
    float b = random(0, 255);

    return color(r, b, g);
}

Note

Of course, another way to write the randomColor() function is as follows:

color randomColor() {
    return color(random(0, 255), random(0, 255), random(0, 255));
}

If we had this function written in our code (where should we put it?), we could simplify the Ball() constructor:

Ball() {
    // We'll insist that the ball starts close to the center of the
    // screen.
    x = random(200, 300);
    y = random(200, 300);

    diam = random(25, 75);

    dx = random(-2.0, 2.0);
    dy = random(-2.0, 2.0);

    // random color
    ballColor = randomColor();
}

20.11. 100 Bouncing Balls

Three bouncing balls are nice, but how could we make 100 balls bounce around the screen? If you think about this for a moment, you will realize that if we have 100 balls, we’ll need 100 variables:

Ball b1;
Ball b2;
Ball b3;
//...
Ball b100;

float gravity;

void setup() {
    size(500, 500);

    b1 = new Ball();
    b2 = new Ball();
    b3 = new Ball();
    // ...
    b100 = new Ball();
}

void draw() {
    background(255);

    // draw the balls
    b1.render();
    b2.render();
    b3.render();
    //...
    b100.render();

    // apply gravity
    b1.dy += gravity;
    b2.dy += gravity;
    b3.dy += gravity;
    //...
    b100.dy += gravity;
}

This program will work (if you complete the missing details), but it is a little impractical. In the next set of notes we will discuss a solution to this problem.

20.12. Questions

  1. What does OOP stand for?

  2. True or false: the name of a constructor is always the same as the name of the class it is inside of.

  3. What is the return type of a constructor?

  4. Consider the following class for representing a 2-dimensional point:

    class Point {
       float x;
       float y;
    }
    1. Write Processing code that creates a new Point object and sets its x variable to 4 and its y variable to 2.
    2. Add a constructor to the Point class that assigns x and y initial values.
  5. Consider this class for representing a person:

    class Person {
       String name;
       int age;
    }
    1. Write Processing code that a new Person object and sets the name variable to Darth Mocha and the age variable to 75.
    2. Add a constructor to the Person class that assigns name and age initial values.
  6. What’s the smallest integer value that random(1, 10) can return? The biggest?

20.13. Programming Questions

  1. As given in the notes, the final version of the Ball class always draws red balls. Lets modify it to draw a ball of any given color. Make the following two changes to Ball:

    1. Add a new variable c of type color under the declaration of diameter inside Ball.

    2. Modify the constructor for Ball so that it takes an initial color as input, e.g. so we can write code like this:

      Ball colorful = new Ball(250, 250, 100, color(0, 140, 0));
    3. Change Balls render() function so that it sets the fill to be c instead of red.

    Test your changes by modifying the program that draws three balls on the screen so that each ball has a different color.

  2. Add a function called getRadius() to the Ball class that returns the radius of the ball (recall that the radius is half the diameter). For example:

    Ball a = new Ball(100, 100, 10);
    float radius = a.getRadius();   // radius is 5
  3. Add a function called getArea() to the Ball class that returns the area of the ball. For example:

    Ball a = new Ball(100, 100, 10);
    float area = a.getArea();   // area is 78.54

    Recall that if a circle has radius r, then its area is \pi r^2.

  4. Using the Ball class, write a program that makes a ball follow the mouse pointer. It should leave no trail.

  5. Using the Ball class, write a program that lets a user drag-and-drop a ball around the screen.

  6. In the constructor for Box, add if-statements that ensure the width and height of the box are both greater than 0. Use exit() to immediately halt the program if either the width or height is 0 or less.

  7. Write a program that uses Box objects to draw the flag of Romania:

    Flag of Romania.
  8. Add a function called getArea() to the Box class that returns the area of the box. For example:

    Box b = new Box(100, 100, 10, 4, color(0));
    float area = b.getArea();   // area is 40
  9. Write your own class called StickFigure that can be used to draw a person in stick-figure form. Your StickFigure class should have at least the following:

    • A constructor that lets you set the (x, y) position of the stick figure on the screen.

    • A function called render() that, when called, draws the stick figure at location (x, y) on the screen.

      You don’t need to make your stick figure drawing very complicated, but it should contain at least three lines and ellipse.

    When it’s done, you should be able to use StickFigure test it using this exact program:

    StickFigure guy;
    
    void setup() {
       size(500, 500);
       smooth();
    
       guy = new StickFigure(200, 100);
    }
    
    void draw() {
       background(255);
    
       guy.render();
    }
  10. Write a program that makes a StickFigure object from the previous question follow the mouse pointer around the screen (without leaving a trail).

  11. Modify the version of Box with angle so that (x, y) refers to the box’s upper-left point instead of its centre. Test this change on a program that makes a rectangle follow the mouse pointer around the screen; the pointer should stay at the upper-left corner of the rectangle no matter its angle.

  12. Modify the Ball() constructor so that the alpha value of the colour is also set randomly. Test it in the bouncing balls program from the notes.