Center Core Never More
February 8th, 2010I tweeted this a while back: a really strange, excellent music video made on an Amiga 500 in the early 90s by YouTube user JimW925.
I tweeted this a while back: a really strange, excellent music video made on an Amiga 500 in the early 90s by YouTube user JimW925.
This is a very brief update, but I’ve just released Ruby PCSet, a Ruby class for pitch-class set operations as open source software. Documentation and examples to come real soon now. Examples are now in the README.
Earlier this week I wrote a tutorial about inheritance and abstract classes in Processing/Java. I mentioned there were several tools we could use to share functionality between classes; the first tool was the abstract class, the second tool is the interface.
First, let’s do a quick review of abstract classes. An abstract class differs from a concrete class in that it can never be directly instantiated. A concrete class must extend the abstract class, and that concrete class may be instantiated. All concrete classes extending an abstract class inherit functionality from a single abstract class—multiple inheritance, or inheritance from more than one class at the same time, is not allowed. Abstract classes may contain both concrete methods and abstract method signatures. Concrete fields and methods are inherited by subclasses. Abstract method signatures define a contract which subclasses must fulfill. For example, if your abstract class contains the code abstract void move(); then all subclasses must define their own void move() method.
Interfaces are a way of defining this kind of contract, but without specifying any functionality along the way. An object which implements an interface is committing to respond in a predictable way to a set of methods. For example, this is how you might specify an interface for objects that can move, and an object that implements this interface:
interface Moves {
void move(int dx, int dy);
}
class Creature implements Moves {
int x, y;
Creature(int initX, int initY) {
x = initX; y = initY;
}
void move(int dx, int dy) {
x += dx; y += dy;
}
}
Note that when declaring a method signature in an interface (as opposed to an abstract class), it is not necessary to prepend abstract, as Processing/Java will figure out what we’re up to and make the method abstract implicitly.
But why use interfaces at all? Why not just use an abstract class, call it MovingThing, and have Creature extend MovingThing? Because while classes can only extend one superclass, they may implement any number of interfaces. For example:
interface Moves {
void move(int dx, int dy);
}
interface ChangesColor {
void changeColor(int newColor);
}
class Creature implements Moves, ChangesColor {
int x, y, c;
Creature(int initX, int initY) {
x = initX; y = initY;
}
void move(int dx, int dy) {
x += dx; y += dy;
}
void changeColor(int newColor) {
c = newColor;
}
}
This is a way to get something a little like multiple inheritance in Processing/Java. Objects implementing these interfaces may not all do the same thing, as they each have the right to their own implementation, but at least we can be sure that they’ll do something when called forth to serve.
An object of any class which implements a given interface guarantees it will implement the methods described in that interface. Because we can be sure that an object of a class which implements an interface will have a specific set of features, Processing/Java allows us to typecast objects to the directly to the interface type. For example, say we’re writing a space shooter game, and we have an ArrayList called listOfSpaceStuff which keeps track of all of our moving objects on the screen. The list will hold objects of types Asteroid, OurHero, and BadGuy. As long as Asteroid, OurHero, and BadGuy all implement the interface Moves, we can do the following:
for(int i = 0; i < listOfSpaceStuff.size(); i++) {
Moves movingObject = (Moves)listOfSpaceStuff.get(i);
movingObject.move(dx, dy);
}
Note that because each of our classes has its own unique implementation of move(), they can all move around the screen in a different way. Also note that we could have created a superclass implementing moves, SpaceThing, made Asteroid, OurHero, and BadGuy subclasses of SpaceThing, and then cast the objects coming out of the list to SpaceThing.
Before the final example, a warning about interfaces. Because interfaces only contain abstract method signatures, they pass along the burden of definition to implementing classes. While changing an abstract class can painlessly enhance the functionality of all its subclasses, changing an interface will more likely break your program, because all of the classes which implement that interface will need to be modified so they fulfill the requirements of the new contract. It pays to do some thinking up-front about how your interfaces are going to work, and whether or not the functionality they describe is likely to change, before using them extensively. (The flip side of this is, of course, that using abstract classes can cause brittle code of an entirely different sort. Interested readers might look to Why extends is evil at JavaWorld for some discussion.)
Here’s an example which uses class inheritance as well as interface inheritance, and typecasts objects to interfaces. Notice that Wall and Chameleon both implement the interface ChangesColor, but in dramatically different ways.
Source code: interfaces
Wall wally = new Wall(50, 150, 0x88000000);
Creature bob = new Creature(150, 150);
Chameleon sally = new Chameleon(250, 150, #FFFFFF);
ArrayList drawList = new ArrayList();
ArrayList colorList = new ArrayList();
ArrayList moveList = new ArrayList();
void setup()
{
size(400,300);
frameRate(30);
drawList.add(wally);
drawList.add(bob);
drawList.add(sally);
colorList.add(wally);
colorList.add(sally);
moveList.add(bob);
moveList.add(sally);
}
void draw()
{
background(222);
for(int i = 0; i < drawList.size(); i++) {
Drawable drawableObject = (Drawable)drawList.get(i);
drawableObject.draw();
}
for(int i = 0; i < colorList.size(); i++) {
ChangesColor colorableObject = (ChangesColor)colorList.get(i);
colorableObject.changeColor(color(random(255), random(255), random(255)));
}
for(int i = 0; i < moveList.size(); i++) {
Moves moveableObject = (Moves)moveList.get(i);
moveableObject.move(int(random(-2,2)), int(random(-2,2)));
}
}
interface Drawable {
void draw();
}
interface Moves {
void move(int dx, int dy);
}
interface ChangesColor {
void changeColor(int newColor);
}
class Creature implements Drawable, Moves {
int x, y;
Creature(int initX, int initY) {
x = initX; y = initY;
}
void move(int dx, int dy) {
x += dx; y += dy;
}
void draw() {
fill(200);
ellipse(x, y, 25, 40);
}
}
class Wall implements Drawable, ChangesColor {
int x, y, c;
Wall(int initX, int initY, int initColor) {
x = initX; y = initY;
c = initColor;
}
void draw() {
fill(c);
rect(x, y, 20, 100);
}
void changeColor(int newColor) {
c = newColor;
}
}
class Chameleon extends Creature implements ChangesColor {
int c, target;
float progress;
Chameleon(int initX, int initY, int initColor) {
super(initX, initY);
c = initColor;
target = initColor;
progress = 1;
}
void changeColor(int newColor) {
if(progress >= 1) {
c = target;
target = newColor;
progress = 0;
}
}
void draw() {
fill(lerpColor(c, target, progress));
ellipse(x, y, 30, 10);
ellipse(x, y, 10, 30);
if(progress < 1) {
progress += 0.05;
}
}
}
Recently I’ve been using Processing for a number of graphics programming projects. There are a lot of web tutorials covering concepts like inheritance, interfaces, inner classes, polymorphism, and so on from a Java perspective, but very few bridge the gap between Java and Processing, and fewer still offer working Processing demonstrations. Further, a great number of Processing tutorials and demos seem to focus on specific animation techniques and not on writing solid, maintable code following best practices. I’m writing a series of tutorials for the advanced beginner or intermediate level Processing user interested in taking their programming skill to the next level.
Prerequisite to understanding this tutorial is having a decent sense of object-oriented programming, specifically how to define and use classes. This tutorial at the Processing website is a good start.
Here’s a common problem for Processing programmers. We have a bunch of objects, lets call them creatures, which are similar in some fundamental ways, but have differences which are not simply superficial. All of the creatures have a shared vocabulary of properties and methods: they all have a width and a height, and they all can be drawn to the screen. However, each creature has one or two actions which are unique. For example, one type of creature might search the screen for food to eat, another might change colors when clicked, and a third might be capable of spontaneous asexual reproduction.
The breadth of difference in functionality might initially suggest coding each creature as a different class, like this:
class Monkey {
int w, h;
Monkey(initWidth, initHeight) {
[...]
}
void draw() {
[...]
}
void lookForFood() {
[...]
}
}
class Chameleon {
int w, h;
Chameleon(initWidth, initHeight) {
[...]
}
void draw() {
[...]
}
void changeColors() {
[...]
}
}
class Amoeba {
int w, h;
Amoeba(initWidth, initHeight) {
[...]
}
void draw() {
[...]
}
void reproduceAsexually() {
[...]
}
}
This is bad for two reasons. First, as Good Programmers, it offends our sensibilities by violating a cardinal rule of our craft: Don’t Repeat Yourself. We are specifying the properties of width and height as well as the constructor and draw() functions three times with no structural variation, so this is two times too many. Second, we’d like to keep track of all our creatures in one big ArrayList. While we’re free to add Monkeys, Chameleons, and Amoebae to an ArrayList, things go wrong when we try to take them out. Processing and Java will require us to cast the object we are getting from the list to a specific type. For example, we might want to do something like this:
for(int i = 0; i < creatureList.size(); i++) {
currentCreature = (Monkey) creatureList.get(i);
currentCreature.draw();
}
But what if the current creature is not, in fact, a Monkey, but instead an Amoeba? Then we’ll get a type error. We could keep separate lists of Monkeys, Chameleons, and Amoebae—but then we’d be repeating ourselves. And what if we later wanted to add GiantSloths or Tardigrades? Our code would be difficult to modify and maintain. Fortunately, we have a few tools which can help us out. The tool we’ll talk about today is the abstract class, which allows use of something called inheritance.
We can specify an abstract class, called Creature, which defines the properties and methods common to every object deigning to call itself creature. We can then implement subclasses of Creature (e.g. Monkey, Chameleon, and Amoeba) which have additional functionality of their own.
abstract class Creature {
int w, h;
Creature(int initWidth, int initHeight) {
w = initWidth; h = initHeight;
}
abstract void draw();
}
class Monkey extends Creature {
Monkey(int initWidth, int initHeight) {
super(initWidth, initHeight);
}
void draw() {
[Draw a monkey.]
}
}
class Chameleon extends Creature {
Chameleon(int initWidth, int initHeight) {
super(initWidth, initHeight);
}
void draw() {
[Draw a chameleon.]
}
}
An abstract class is like a partially filled-in template for subclasses. Members of an abstract class can never be directly instantiated (that is, created directly). Given the code above, for example, it would make no sense to write something like Creature aCreature = new Creature(2,3);. Instances of classes like Monkey and Chameleon can be Creatures, but there’s no such thing as an object which is just a Creature and nothing else. Every subclass of Creature inherits its methods and variables. In this case, that means that Monkeys and Chameleons have integers for storing their width and height.
Abstract classes also make demands of their subclasses. The code abstract void draw(); means that every subclass of Creature is required to implement a function of type void called draw(). If a subclass didn’t implement this draw() function, we’d get an error. This set of requirements is like a contract that each subclass must honor. I’m going to expand on this notion of contractual obligation in the next tutorial, which will include a section on another tool we could use for this purpose: interfaces.
Additionally, something funny is happening in those subclass constructors. Subclasses are allowed to call the constructor function of their superclass, in this case Creature(), by using the method super(). The Monkey() and Chameleon() constructors just pass along the initial width and height values to the superclass constructor which handles the value assignment. If a subclass has additional variables not common to the superclass, those should also be initialized in the constructor. For example, Chameleons might want to store their current color in a variable, and begin life with a color passed to the constructor. We’d modify the Chameleon subclass as follows:
class Chameleon extends Creature {
int c;
Chameleon(int initWidth, int initHeight, int initColor) {
super(initWidth, initHeight);
c = initColor;
}
void draw() {
[Draw a chameleon.]
}
}
Now we can populate an ArrayList with Monkeys and Chameleons, then iterate through that list, casting each item to the type Creature. Because the Creature superclass requires all its subclasses implement the draw() method, we can safely call this method on any Creature we get from the ArrayList. Here’s how that looks:
ArrayList creatureList = new ArrayList();
Monkey hanuman = new Monkey(4,8);
Chameleon lizzy = new Chameleon(7,3);
creatureList.add(hanuman);
creatureList.add(lizzy);
for(int i = 0; i < creatureList.size(); i++) {
currentCreature = (Creature) creatureList.get(i);
currentCreature.draw();
}
Mighty convenient. Now if we add additional variables or methods to the creature class, all of its subclasses inherit that functionality while preserving their unique features. Below is a complete working demonstration of these principles in action, using Ellipsoids and Rectithings, creatures which are much easier to draw than apes and lizards.
Click on the sketch to give it focus, then press the spacebar to move all the creatures to the center of the screen.
This browser does not have a Java Plug-in.
Get the latest Java Plug-in here.
Source code: abstractClasses
// Create three creatures.
Ellipsoid jane = new Ellipsoid(100, 50, 300, 150);
Rectithing alan = new Rectithing(40, 15, 100, 50);
Ellipsoid bob = new Ellipsoid(10, 70, 150, 200);
// Create our ArrayList for creature storage.
ArrayList creatureList = new ArrayList();
void setup()
{
size(400,300);
frameRate(30);
// Add our creatures to the list.
creatureList.add(jane);
creatureList.add(alan);
creatureList.add(bob);
}
void draw()
{
background(222);
// Iterate through the list of creatures, telling each one to draw
// and move.
for(int i = 0; i < creatureList.size(); i++) {
Creature currentCreature = (Creature) creatureList.get(i);
currentCreature.draw();
currentCreature.move();
}
}
// When the spacebar is pressed, iterate through the creatureList
// and move every creature to the center.
void keyReleased() {
if(key == ' ') {
for(int i = 0; i < creatureList.size(); i++) {
Creature currentCreature = (Creature) creatureList.get(i);
currentCreature.moveToCenter();
}
}
}
abstract class Creature {
// Every subclass will inherit these variables.
int w, h, x, y;
Creature(int initWidth, int initHeight, int initX, int initY) {
w = initWidth; h = initHeight;
x = initX; y = initY;
}
// Every sublcass will inherit this method.
void moveToCenter() {
x = int(width * 0.5);
y = int(height * 0.5);
}
// Every subclass is required to implement these methods on their own.
abstract void draw();
abstract void move();
}
class Ellipsoid extends Creature {
// Ellipsoids have a changing color.
int c;
Ellipsoid(int initWidth, int initHeight, int initX, int initY) {
// Call the superclass constructor on initialization.
super(initWidth, initHeight, initX, initY);
c = int(random(255));
}
void draw() {
fill(c);
ellipse(x,y,w,h);
}
void move() {
x += int(random(-2,2));
y += int(random(-2,2));
w += int(random(-3,3));
h += int(random(-3,3));
c += int(random(-10,10));
}
}
class Rectithing extends Creature {
Rectithing(int initWidth, int initHeight, int initX, int initY) {
super(initWidth, initHeight, initX, initY);
}
void draw() {
fill(127);
rect(x,y,w,h);
}
void move() {
x += int(random(-3, 3));
y += int(random(-3, 3));
}
}
My good friend Jack Perkins sent me this video of Leif Shackleford cutting up his laptop with a table saw. With what sounds like live electronic processing. Scenes need a shake-up like this every once in a while; it’s far too easy to get complacent crawling around on the floor tapping on your collection of Boss digital distortion pedals from the 90s or whatever. Bring the noise, brother Leif!