polymorphism

One of the most interesting features of OOP is how classes can extend others. The subclasses inherit the properties of their parent by default, and can add their own (override) to further specify their definition. The ability of each class to respond in its own way to the same message is called polymorphism.

The following example illustrates the parent-child inheritance model.

// ----------------------------------------------------------------------
// CLASS DEFINITIONS
// ----------------------------------------------------------------------
class Parent {
  // constructor
  Parent() {
    println("Building a Parent.");
  }

  void speak() {
    println("I am a Parent.");
  }
}

// use the keyword "extends" to create a child/subclass
class Child extends Parent {
  void speak() {
    println("I am a Child.");
  }
}

// ----------------------------------------------------------------------
// GLOBAL VARIABLES
// ----------------------------------------------------------------------
// declare a Parent object
Parent p;
// declare a Child object
Child c;

// ----------------------------------------------------------------------
// MAIN
// ----------------------------------------------------------------------
void setup() {
  // instantiate the Parent object
  p = new Parent();
  // instantiate the Child object
  c = new Child();

  p.speak();
  c.speak();
}

The output of that last application would be:

Building a Parent.
Building a Parent.
I am a Parent.
I am a Child.

Note that:

Overriding is quite useful since it allows a child to inherit most of the functionality of one class, but also to change some of it by replacing one or more of the parent's methods. The overridden methods will have the same signature (i.e. the same name, parameter count, parameter types, and return type), but their functionality will be different. This allows the programmer to keep the same interface, the outward definition of the class, while changing its inner workings.

abstract classes

If there ever is a need to prevent a child class from overriding one of its parent's methods, the final modifier must appear in the parent's method declaration.

class Parent {
  // constructor
  Parent() {
    println("Building a Parent.");
  }

  final void speak() {
    println("I am a Parent.");
  }
}				

On the other hand, we can also do the opposite, and force the child to override one of its parent's methods. This is done with the abstract modifier and means that the method is incomplete and must be overridden. Note that if a class contains an abstract method, it must also be defined as an abstract class. Note that an abstract class cannot be instantiated.

abstract class Parent {
  // constructor
  Parent() {
    println("Building a Parent.");
  }

  abstract void speak();
}				

strength in numbers revisited

Let's modify the strength in numbers applet so that we can create three types of Soldiers: Circles, Squares, and Triangles. All three types will have the same attributes and will behave the same way. The only difference is their appearance, so the only method that will need modification is draw(). Since a general Soldier does not have enough information to know how to draw itself, that method is a good candidate to convert to abstract.

abstract class Soldier {
  // ...

  // --------------------------------------------------------------------
  // METHODS
  // --------------------------------------------------------------------
  
  // ...

  /* draw() has to be implemented by any class extending Soldier */
  abstract void draw();

  // ...
}

class Circle extends Soldier {
  Circle(float x, float y) {
    super(x, y);
  } 

  /* draws the soldier */
  void draw() {
    // draw the shape first
    fill(colour);
    ellipse(xPos, yPos, strength+MIN_SIZE, strength+MIN_SIZE);
    
    // draw the number over it
    fill(0);
    text(strength, xPos, yPos);
  }
}

class Square extends Soldier {
  Square(float x, float y) {
    super(x, y);
  } 
  
  /* draws the soldier */
  void draw() {
    // draw the shape first
    fill(colour);
    rectMode(CENTER);
    rect(xPos, yPos, strength+MIN_SIZE, strength+MIN_SIZE);
    rectMode(CORNER);
    
    // draw the number over it
    fill(0);
    text(strength, xPos, yPos);
  }
}

class Triangle extends Soldier {
  Triangle(float x, float y) {
    super(x, y);
  } 
  
  /* draws the soldier */
  void draw() {
    // draw the shape first
    fill(colour);
    float half = (strength+MIN_SIZE)/2.0;
    triangle(xPos, yPos-half, xPos-half, yPos+half, xPos+half, yPos+half);
    
    // draw the number over it
    fill(0);
    text(strength, xPos, yPos);
  }
}

Note that:

We can now update the main application. Since all objects we are creating are still Soldiers, we don't need to modify the array declaration. However, since we can't instantiate a Soldier (because it is an abstract class), we need to specify which kind of Soldier to create in addSoldier().

// ...
				
// ----------------------------------------------------------------------
// USER FUNCTIONS
// ----------------------------------------------------------------------
/* adds a new soldier to the display */
void addSoldier(int newX, int newY) {
  if (numSoldiers < MAX_SOLDIERS) {
    // randomly pick the type of Soldier to create
    float coin = random(1);
    if (coin < .33) {
      army[numSoldiers] = new Circle(newX, newY);
    } else if (coin < .66) {
      army[numSoldiers] = new Square(newX, newY);
    } else {
      army[numSoldiers] = new Triangle(newX, newY);
    }

    numSoldiers++;
  }
}

// ...

We can even use this new class hierarchy to make the game more interesting. Let's add a rule stating that a Soldier can only fight with a Soldier of a different type, i.e. a Circle can only fight Squares and Triangles, a Square can only fight Circles and Triangles, and a Triangle can only fight Circles and Squares. We'll implement this by overriding the collidesWith() method for each subclass to check the type of the passed Soldier before actually checking for collisions. If the types are different, we proceed as usual, using the collidesWith() definition from Soldier; if they are the same, we'll just assume they don't collide.

class Circle extends Soldier {
  // ...

  /* if the other Soldier is not of the same type, makes them collide */
  boolean collidesWith(Soldier other) {
    if (!(other instanceof Circle)) {
      return super.collidesWith(other);
    }
    return false; 
  }
}

class Square extends Soldier {
  // ...

  /* if the other Soldier is not of the same type, makes them collide */
  boolean collidesWith(Soldier other) {
    if (!(other instanceof Square)) {
      return super.collidesWith(other);
    }
    return false; 
  }
}

class Triangle extends Soldier {
  // ...

  /* if the other Soldier is not of the same type, makes them collide */
  boolean collidesWith(Soldier other) {
    if (!(other instanceof Triangle)) {
      return super.collidesWith(other);
    }
    return false; 
  }
}

Processing Workshop

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