object-oriented programming

Object-oriented programming (OOP) is an approach to programming based on interaction between objects. This is in contrast to the procedural paradigm we have been learning to date, where program execution proceeds via a series of function calls. In practice, there is little difference between the OOP and the procedural approaches; any programming challenge that can be met with one method can be met with the other.

There are several reasons for using OOP, including:

Taking an OOP approach may seem like overkill for simple programs, but it makes creating and maintaining more complex programs a lot easier.

An object (or class) is a high-level data type. Unlike most types we have seen so far (int, float, char, ...), which only hold values, objects can hold both data and member functions. These functions are called methods, and they define what an object can do, as well as how it interacts with other objects.

You can think of an object as an entity which has properties (variables) and behaviours (functions). This may lead you to believe that every sketch we have created until now is an object, and you would be right.

using objects

definition

When we define an object, we lay out a blueprint that will be used to describe all objects of that type. Such blueprints for objects are called classes. To define a class, we use the class keyword followed by a name, which is CamelCased (with an uppercase first letter) by convention. A set of curly braces follows the name.

class ClassName {
	
}				

We then group all properties and methods that belong to this class by placing them inside the curly braces.

class ClassName {
  type variableName;
  type variableName;

  returnType functionName(argType argument1, argType argument2) {
    // local variables
    // body
    return value;
  }

  returnType functionName(argType argument1, argType argument2) {
    // local variables
    // body
    return value;
  }
}				

One of the methods will be used to initialize the object when it is created, kind of like the setup() function in our sketch. This method is called a constructor. For Processing to know which of the methods in the class is the constructor, it needs to have the following properties:

class ClassName {
  type variableName;
  type variableName;

  // constructor
  ClassName(argType argument1, argType argument2) {
    // local variables
    // body
  }

  returnType functionName(argType argument1, argType argument2) {
    // local variables
    // body
    return value;
  }

  returnType functionName(argType argument1, argType argument2) {
    // local variables
    // body
    return value;
  }
}				

instantiation

The action of creating an object from a class definition is referred to as instantiating the class. Classes we define are treated as a new data type, and the syntax used when instantiating objects is therefore very similar to creating a variable of a primitive type. Since an object is more than a single datum, the right side of the assignment operator should call the class constructor instead of just holding a value. This is done using the keyword new.

ClassName instanceName = new ClassName(parameters);   

call

To reference an object, we just use its variable name. To reference the properties and methods of an object, we use the dot syntax.

// references a property of an object
instanceName.property = 0;  

// calls a method of an object; methods are functions defined on an object
instanceName.method(parameters);      

You may remember at this point that the String type used the dot syntax (e.g. someString.charAt(5)), and this is because Strings are also objects.

a concrete example

We will convert the pulse we built to use OOP. In order to instantiate the pulse as an object, we have to define a class, or template. Let's call that class Pulse:

class Pulse {

}									

We know that the properties that each pulse must keep track of are its position and size. Each pulse can have different values for each of these properties, making them good candidates as class attributes.

class Pulse {
  /* attributes */
  int SCALE = 10;
  int x;    // x-coordinate of the pulse
  int y;    // y-coordinate of the pulse
  float s;  // size of the pulse
}									

Although the Pulse now has some identity, it still doesn't do anything. We need to create some methods to correct this. The first one we need to write is the constructor, which will be called whenever we instantiate a new object from the Pulse class.

class Pulse {
  /* attributes */
  int SCALE = 10;
  int x;    // x-coordinate of the pulse
  int y;    // y-coordinate of the pulse
  float s;  // size of the pulse

  /* methods */
  // constructor
  Pulse(int pulseX, int pulseY) {
    x = pulseX;
    y = pulseY;
    s = 0; 
  }
}

We can now proceed with the action methods. If we examine the previous pulse sketch, we see that each Pulse needs to be able to move and draw itself:

class Pulse {
  /* attributes */
  int SCALE = 10;
  int x;    // x-coordinate of the pulse
  int y;    // y-coordinate of the pulse
  float s;  // size of the pulse

  /* methods */
  // constructor
  Pulse(int pulseX, int pulseY) {
    x = pulseX;
    y = pulseY;
    s = 0; 
  }

  // moves the pulse to the given position
  void move(int newX, int newY) {
    x = newX;
    y = newY;
  }

  // draws the pulse and updates its size
  void draw() {
    fill(255);
    ellipse(x, y, sin(s)*SCALE, sin(s)*SCALE);
    s += 0.1;
  }
}									

Our class is now defined and we can use it in our applet.

// ----------------------------------------------------------------------
// GLOBAL VARIABLES
// ----------------------------------------------------------------------
Pulse aPulse;

// ----------------------------------------------------------------------
// BUILT-IN FUNCTIONS
// ----------------------------------------------------------------------
void setup() {
  size(400, 400);
  smooth();
  noStroke();

  // instantiate the Pulse
  aPulse = new Pulse(width/2, height/2);
}

void draw() {
  background(0);

  // draw the pulse
  aPulse.draw();
}

void mousePressed() {
  aPulse.move(mouseX, mouseY);
}

We can now use the Pulse class in the version where a new Pulse is drawn every time the mouse is pressed or dragged. Since each Pulse manages its own position and size, we don't need to create arrays to hold this information. In fact, all we need is a single array of Pulses.

// ----------------------------------------------------------------------
// GLOBAL CONSTANTS
// ----------------------------------------------------------------------
int MAX_PULSES = 500;

// ----------------------------------------------------------------------
// GLOBAL VARIABLES
// ----------------------------------------------------------------------
int numPulses = 0;
Pulse pulses[] = new Pulse[MAX_PULSES];

// ----------------------------------------------------------------------
// BUILT-IN FUNCTIONS
// ----------------------------------------------------------------------
void setup() {
  size(400, 400);
  smooth();
  noStroke();
}

void draw() {
  background(0);

  // draw all the pulses
  for (int i=0; i < numPulses; i++) {
    pulses[i].draw(); 
  }
}

void mousePressed() {
  addPulse(mouseX, mouseY); 
}

void mouseDragged() {
  addPulse(mouseX, mouseY); 
}

void keyPressed() {
  if (key == 32) {  // space bar
    // clear all
    numPulses = 0;
  } 
}

// ----------------------------------------------------------------------
// USER FUNCTIONS
// ----------------------------------------------------------------------
/* adds a new pulse to the list */
void addPulse(int newX, int newY) {
  if (numPulses < MAX_PULSES) {
    pulses[numPulses] = new Pulse(newX, newY);
    numPulses++;
  }
}				

Processing Workshop

Elie Zananiri
Alberta College of Art + Design
3-5 April 2008