Source Code. Projects. Nerd Stuff. Art Stuff.

Lesson 7: Objects

[gn_pullquote align="left"]Object-oriented programming (OOP) is a different way to think about your programs, and objects are the key to understanding OOP. Unlike the primitive data types boolean, int, and float, which can store only one value, an object can store many. Objects are also a way to group variables with related functions. We’ve already been working with variables and functions, objects simply combine these into a more understandable package.[/gn_pullquote]

Objects are important because they break up ideas into smaller building blocks. This mirrors the natural world where, for instance, organs are made of tissue, tissue is made of cells, and so on. Similarly, as your code becomes more complicated, you will need to think in terms of smaller structures that form more complicated ones. It’s easier to write and maintain smaller, understandable pieces of code that work together than it is to write one large piece of code that does everything at once.

Look around right now and you’ll find many examples of real-world objects: your dog, your desk, your television set, your bicycle. Real-world objects share two characteristics: They all have state and behavior. Dogs have state (name, color, breed, hungry) and behavior (barking, fetching, wagging tail). Bicycles also have state (current gear, current pedal cadence, current speed) and behavior (changing gear, changing pedal cadence, applying brakes).

Identifying the state and behavior for real-world objects is a great way to begin thinking in terms of object-oriented programming. A software object is a collection of related variables and functions. In the context of objects, a variable is called a field (or instance variable) and a function is called a method. Fields and methods work just like the variables and functions covered in earlier chapters, but we’ll use the new terms to emphasize that they are a part of an object. To say it another way, an object combines related data (fields) with related actions and behaviors (methods). The idea is to group together related data with related methods that act on that data.

The fields contained within the object are local to the object, and therefore hidden from the rest of the code. Hiding internal state and requiring all interaction to be performed through an object’s methods is known as data encapsulation — a fundamental principle of object-oriented programming.

Take a minute right now to observe the real-world objects that are in your immediate area. For each object that you see, ask yourself two questions: “What possible states can this object be in?” and “What possible behavior can this object perform?”. You’ll notice that real-world objects vary in complexity; your desktop lamp may have only two possible states or fields (on and off) and two possible behaviors or methods (turn on, turn off), but your desktop radio might have additional states (on, off, current volume, current station) and behavior (turn on, turn off, increase volume, decrease volume, seek, scan, and tune). You may also notice that some objects, in turn, will also contain other objects. These real-world observations all translate into the world of object-oriented programming.

Consider a bicycle, for example:

By attributing state (current speed, current pedal cadence, and current gear) and providing methods for changing that state, the object remains in control of how the outside world is allowed to use it. For example, if the bicycle only has 6 gears, a method to change gears could reject any value that is less than 1 or greater than 6.

Bundling code into individual software objects provides a number of benefits, including:

  1. Modularity: The source code for an object can be written and maintained independently of the source code for other objects. Once created, an object can be easily passed around inside the system.
  2. Information-hiding: By interacting only with an object’s methods, the details of its internal implementation remain hidden from the outside world.
  3. Code re-use: If an object already exists (perhaps written by another software developer), you can use that object in your program. This allows specialists to implement/test/debug complex, task-specific objects, which you can then trust to run in your own code.
  4. Pluggability and debugging ease: If a particular object turns out to be problematic, you can simply remove it from your application and plug in a different object as its replacement. This is analogous to fixing mechanical problems in the real world. If a bolt breaks, you replace it, not the entire machine.

Classes and Objects

In the real world, you’ll often find many individual objects all of the same kind. There may be thousands of other bicycles in existence, all of the same make and model. Each bicycle was built from the same set of blueprints and therefore contains the same components. In object-oriented terms, we say that your bicycle is an instance of the class of objects known as bicycles, and each instance has its own set of fields and methods. A class is the blueprint from which individual objects are created.

Define a Class

Before you can create an object, you must define a class. Before writing a class, it’s helpful to plan ahead. Think about what fields and methods your class should have. Do a little brainstorming to imagine all the possible options and then prioritize and make your best guess about what will work. You’ll make changes during the programming process, but it’s important to have a good start.

For your fields, select clear names and decide the data type for each. The fields inside a class can be any type of data. A class can simultaneously hold many booleans, floats, images, strings, and so on. Keep in mind that one reason to make a class is to group together related data elements. For your methods, select clear names and decide the return values (if any). The methods are used to change the values of the fields and to perform actions based on the fields’ values.

For our first class, we’ll convert an example used earlier in this series. We start by making a list of the fields from the example:

1
  float x
1
  float y
1
  int diameter
1
  float speed

The next step is to figure out what methods might be useful for the class. In looking at the draw() function from the example we’re adapting, we see two primary components. The position of the shape is updated and drawn to the screen. Let’s create two methods for our class, one for each task:

1
  void move()
1
  void display()

Neither of these methods return a value, so they both have the return type void. When we next write the class based on the lists of fields and methods, we’ll follow four steps:

  1. Create the block.
  2. Add the fields.
  3. Write a constructor (explained below) to assign values to the fields.
  4. Add the methods.

First, we create a block:

  class JitterBug {

  }

 

Notice that the keyword class is lowercase and the name JitterBug is uppercase. Naming the class with an uppercase letter isn’t required, but it is a convention used to denote that it’s a class. (The keyword class, however, must be lowercase because it’s a rule of the programming language.)

Second, we add the fields. When we do this, we have to decide which fields will have their values assigned through a constructor, a special method used for that purpose. As a rule of thumb, field values that you want to be different for each class are passed in through the constructor, and the other field values can be defined when they are declared. For the JitterBug class, we’ve decided that the values for x, y, and diameter will be passed in. So the fields are declared as follows:

  class JitterBug {
    float x;
    float y;
    int diameter;
    float speed = 0.5;
  }

 

Third, we add the constructor. The constructor always has the same name as the class. The purpose of the constructor is to assign the initial values to the fields when an object (an instance of the class) is created. The code inside the constructor block is run once when the object is first created. As discussed earlier, we’re passing in three parameters to the constructor when the object is initialized. Each of the values passed in is assigned to a temporary variable that exists only while the code inside the constructor is run. To clarify this, we’ve added the name temp to each of these variables, but they can be named with any terms that you prefer. They are used only to assign the values to the fields that are a part of the class. Also note that the constructor never returns a value and therefore doesn’t have void or another data type before it. After adding the constructor, the class looks like this:

  class JitterBug {

    float x;
    float y;
    int diameter;
    float speed = 0.5;

    JitterBug(float tempX, float tempY, int tempDiameter) {
      x = tempX;
      y = tempY;
      diameter = tempDiameter;
    }
  }

 

The last step is to add the methods. This part is straightforward; it’s just like writing functions, but here they are contained within the class. Also, note the code spacing. Every line within the class is indented a few spaces to show that it’s inside the block. Within the constructor and the methods, the code is spaced again to clearly show the hierarchy:

  class JitterBug {

    float x;
    float y;
    int diameter;
    float speed = 0.5;

    JitterBug(float tempX, float tempY, int tempDiameter) {
      x = tempX;
      y = tempY;
      diameter = tempDiameter;
    }

    void move() {
      x += random(-speed, speed);
      y += random(-speed, speed);
    }

    void display() {
      ellipse(x, y, diameter, diameter);
    }
  }

 

Making an Object

Now that we have defined a class, to use it in a program we must define an object from that class. There are two steps to create an object:

  1. Declare the object variable.
  2. Create (initialize) the object with the keyword new.

  JitterBug bug;  // Declare Object

  void setup() {
    size(480, 120);
    smooth();
    // Create object and pass in parameters
    bug = new Jitterbug(width/2, height/2, 90);
  }

  void draw() {
    bug.move;
    bug.display();
  }

  // Put a copy of the JitterBug class here

 

Each class is a data type and each object is a variable. We declare object variables in a similar way to variables from primitive data types like boolean, int, and float. The object is declared by stating the data type followed by a name for the variable:

1
JitterBug bug;

The second step is to initialize the object with the keyword new. It makes space for the object in memory and creates the fields. The name of the constructor is written to the right of the new keyword, followed by the parameters into the constructor, if any:

1
JitterBug bug = new JitterBug(200.0, 250.0, 30);

The three numbers within the parentheses are the parameters that are passed into the JitterBug class constructor. The number of these parameters and their data types must match those of the constructor.

Making Multiple Objects

In the last example, we saw something new: the period (dot) that’s used to access the object’s methods inside of draw(). The dot operator is used to join the name of the object with its fields and methods. This becomes clearer in this example, where two objects are made from the same class. The jit.move() command refers to the move() method that belongs to the object named jit, and bug.move() refers to the move() method that belongs to the object named bug:

  JitterBug jit;
  JitterBug bug;

  void setup() {
    size(480, 120);
    smooth();
    jit = new Jitterbug(width * 0.33, height/2, 50);
    bug = new Jitterbug(width * 0.66, height/2, 10);
  }

  void draw() {
    jit.move;
    jit.display();
    bug.move;
    bug.display();
  }

  // Put a copy of the JitterBug class here

 

Now that the class exists as its own module of code, any changes will modify the objects made from it. For instance, you could add a field to the JitterBug class that controls the color, or another that determines its size. These values can be passed in using the constructor or assigned using additional methods, such as setColor() or setSize(). And because it’s a self-contained unit, you can also use the JitterBug class in another sketch.

In Processing, a new tab is usually created for each class, which reinforces the modularity of working with classes and makes the code easy to find. To create a new tab, click on the arrow at the righthand side of the tab bar. When you select New Tab from the menu, you will be prompted to name the tab within the message window. Using this technique, you can modify this example’s code to make a new tab for the JitterBug class.

Inheritance

We’ve seen in the previous examples that different kinds of objects often have a certain amount in common with each other, yet each also defines additional features that make them different: tandem bicycles have two seats and two sets of handlebars; road bikes have drop handlebars; some mountain bikes have an additional chain ring, giving them a lower gear ratio.

Object-oriented programming allows classes to inherit commonly used state and behavior from other classes. In this example, 

1
Bicycle

 now becomes the superclass of 

1
MountainBike

1
RoadBike

, and 

1
TandemBike

. In the Java programming language, each class is allowed to have one direct superclass, and each superclass has the potential for an unlimited number of subclasses:

The syntax for creating a subclass is simple. At the beginning of your class declaration, use the extends keyword, followed by the name of the class to inherit from:

1
 
1
class MountainBike <strong>extends</strong>

This gives 

1
MountainBike

 all the same fields and methods as 

1
Bicycle

, yet allows its code to focus exclusively on the features that make it unique. This makes code for your subclasses easy to read. However, you must take care to properly document the state and behavior that each superclass defines, since that code will not appear in the source file of each subclass.