Object-oriented programming

20.2.2012

When creating a computer game one must think about many different things. Imagine a simple computer game where a hero jumps from platform to platform, avoids enemy monsters, collects keys, and after collecting all keys can enter the door and appear on the next map. What has to be solved in such game? Positions of the hero, monsters, keys, door, platforms; graphical files containing all pictures; moving the hero by keyboard; a few algorithms for moving monsters; movement of the hero during jump, hitting a wall, colliding with monsters, taking a key, entering the door; how many keys have yet to be collected, how high is the score, what map are we in; how are individual maps designed; also some introduction screen and final screens for winning or losing the game.

Even a simple game contains many things. To be able to handle so many different details and not get confused, we need to divide the program to smaller parts; in each part we focus on the relevant topics and for a moment ignore the unrelated topics. If we divide the program wisely, the resulting parts will be rather easy. Dividing the program well is an important part of programmer's craft.

Object-oriented programming is a way of programming that forces us to divide programs to small parts called objects. It can not be explained in one article, but I will try to show the point using a few examples. (And the rest you can find in textbooks and online.) To make these examples as simple as possible, let's imagine a program working with a few geometric shapes, such as squares. Each square has a size, which can be used o calculate its perimeter and area. The program will create some squares and then calculate their total area.

Class

The individual squares differ from each other in lengths of their sides (differet values), but share the formulas for calculating the perimeter and area (same program). From programmer's viewpoint they will be different objects of the same class, class "Square". Now we have divided our program into two smaller parts. The main program knows how big squares it wants to create, and that it intends to calculate their total area later. The "Square" class knows square-related things, in this case a formula to calculate the area. The "Square" class does not care about how many squares will exist and how they will be used. The main program does not care about formula for square area; if it needs to know one, it will simply ask "you, square, what is your area?" and receive an answer. Generally speaking, the main program knows what to do with objects, and the object knows how to do it. Here is an example program:

class Square {

	int side;

	int area() {
		return side * side;
	}

}

public class Example {

	public static void main(String[] args) {
		Square small = new Square();
		small.side = 5;
		Square big = new Square();
		big.side = 10;
		System.out.println(small.area() + big.area());
	}

}

The "new" command creates a new object of given class. To use this object later, we put it into a variable. If we follow the variable name with a dot, we communicate with the object stored in this variable. Command "small.side" sets the variable "side" of the square stored in the variable "small". Command "small.area()" call the function "area()" of an object stored in the variable "small".

With the program thus divided, all square-related things will be written in the "Square" class. If we ever find out that the square area is calculated incorrectly, we will seek for error in the "Square" class. For higher clarity we can put the "Square" class to a separate file. If we have many such files, we can group them into various directories (called "packages" in Java).

To demonstrate why it is useful to separate square calculations from the rest of the program, let's pretend that multiplying is a very difficult operation taking a lot of time. (This is not really true, but we need a pretense for the following program changes.) To save time, the square can remember the calculated area in a variable, and if the main program asks about the size again, the square will not calculate again, but return a remembered result instead. This improved square will be called "TurboSquare".

class TurboSquare {

	int side;
	int calculatedArea = 0;

	int area() {
		if (0 == calculatedArea) {
			calculatedArea = side * side; 
		}
		return calculatedArea;
	}

}

Extending class

If we want to use in our program both the original squares and the turbosquares, we have two similar classes. From data viewpoint, the "TurboSquare" class contains the same data ("side" variable) as the "Square" class, plus something more ("calculatedArea" variable). From function viewpoint, "TurboSquare" has the same functions as "Square" ("area()" function), although the function code is different. When two classes are in such relation, we can make the "TurboSquare" class an extension of the "Square" class. Then we do not have to write what these two classes have in common, only the differences. In our case the only shared part is the "side" variable, but with more complex classes the shared part could be larger and this would save us a lot of unnecessary writing.

class TurboSquare extends Square {

	int calculatedArea = 0;

	@Override
	int area() {
		if (0 == calculatedArea) {
			calculatedArea = side * side; 
		}
		return calculatedArea;
	}

}

The word "extends" means that this class extends another class. The "@Override" annotation before function shows that this function existed in the original class too, but in the extended class it is modified. (The "@Override" annotation is voluntary, but it is worth writing to make the difference between the original and extended class more obvious.)

What is the advantage of dividing the program into objects? If we return to the main program now, we only have to change the "Square small = new Square();" line to "Square small = new TurboSquare();" line. The program will start using the new functionality, and it does not look more complex. All the complexity related to calculating the square area is in the relevant class; it does not encumber the rest of the program. Note that the object from the extended class can be stored in a variable of the base class, so the rest of the program does not have to even know it is working with a new type of object. The "Square" variable can contain either a "Square" type or a "TurboSquare" type object; the program can set size to any of them, and ask the area of any of them.

Accessor functions

In our example there may be a problem if we change the turbosquare size during the program. Look at the following program: the turbosquare first calculates its area, but then returns the remembered value despite the side length has changed.

	public static void main(String[] args) {
		Square s = new TurboSquare();
		s.side = 5;
		System.out.println(s.area());
		s.side = 10;
		System.out.println(s.area()); // wrong result :-(
	}

To avoid this kind of mistakes, we should now allow the rest of the program to access our object's variables directly. The rest of the program does not know (and should not know) about the relations between these variables. So if it needs to set or get their values (how could we use a square if we could not set its side?), we have to create specialized functions, called accessors; traditionally they are called "set..." and "get..." with the name of given variable. A safely coded square would then look like this:

class Square {

	protected int side;

	public int getSide() {
		return side;
	}

	public void setSide(int side) {
		this.side = side;
	}

	public int area() {
		return side * side;
	}

}

The word "protected" means that only the class "Square" and its extensions can access the "side" variable directly. The word "public" means that anyone can use these functions. In our situation, anyone can ask of modify the side, but only using the functions, not directly. The main program will then look like this:

	public static void main(String[] args) {
		Square s = new TurboSquare();
		s.setSide(5);
		System.out.println(s.area());
		s.setSide(10);
		System.out.println(s.area());
	}

If the "TurboSquare" object does not like to have side changed unexpectedly, it only has to modify the "setSide" function (the only funtion that can change the side length) like this. The word "super" means the base class for this extended class, so in this situation "super.setSide" means the function "setSide" as defined for the "Square" class.

	@Override
	public void setSide(int side) {
		super.setSide(side);
		calculatedArea = 0;
	}

Let's add another geometrical shape, a rectangle, represented by a "Rectangle" class. Thanks to our recently gained experience we can write in directly, with protected variables and accessor functions.

class Rectangle {

	private int height;
	private int width;

	public void setHeight(int height) {
		this.height = height;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public int getWidth() {
		return width;
	}

	public int area() {
		return height * width;
	}

}

A beginner who has recently learned about extending classes may be tempted to search for a special relationship between square and rectangle. A square is just a special type of a rectangle with two equally long sides, isn't it? On the other hand, a rectangle is just a square with additional numeric value, isn't it?

When programming, you should consider one type a subtype of another only if the first type can be safely used in any situation where the second type is used. For example we can use the "TurboSquare" safely in any place where the "Square" was previously used. How is it with the square and the rectangle?

If one type is a subtype of another, it must provide all its functions. This could be achieved by adding methods "setHeight" and "setWidth" to a square; both methods would set its side. On the other hand, method "setSide" could set both height and width of a rectangle. But would it work correctly?

When using a rectangle, we expect that it we set its sides to 3 and 5, and then ask about its area, it will return number 15. A square cannot fulfill this expectation; therefore a square cannot be a subtype of a rectangle. On the other hand we expect that a square, however it is set, has an area equal to a second power of its height; a rectangle does not fulfill this expectation, therefore a rectangle cannot be a subtype of a square. Neither of these types is a subtype of another; neither should be programmed as an extended class.

Interface

However, squares and rectangles have something in common, from the viewpoint of the main program. They are geometric shapes able to calculate their area. This kind of relation is called interface; the objects share neither variables nor code, but they provide the same functions. An interface "Shape" would be:

interface Shape {
	public int area();
}

To specify that squares and rectangle are shape, we change the starting words "class Square" to "class Square implements Shape", and similarly "class Rectangle" to "class Rectangle implements Shape". Their "area()" functions can be then annotated by "@Override". By the way, all functions used in an interface must have "public" visibility. We do not have to specify that the class "TurboSquare" is a shape; as an extension of the "Square" class it inherits all interfaces automatically.

Since now we can put objects of types "Square", "TurboSquare" and "Rectanle" into variables of type "Shape".

Constructor

When we make a new shape in our program, we set its properties (side for a square, height and width for a rectangle). This is not a perfect solution. First, it has too many lines; second, we could forget to set some property. We can use a special type of function called constructor which sets object properties at its creation. Constructor is written like a function, just instead of the return type and the function name we write the name of given class. Constructor for a "Rectangle" class could look like this:

	public Rectangle(int height, int width) {
		this.height = height;
		this.width = width;
	}

A new rectangle can the be created by a command:

Rectangle r = new Rectangle(3, 5);

In fact, every type has a constructor, or more of them. If we do not specify any constructor in our program, Java will create automatically a constructor without parameters, which would have explicitly looked like this:

public Type() {
	super();
}

If we create a new constructor for the "Square" class...

	public Square(int side) {
		this.side = side;
	}

...then we must also adjust the "TurboSquare" class constructor to use the base class constructor:

	public TurboSquare(int side) {
		super(side);
	}

New objects can then be created like this:

		Square s1 = new Square(2);
		Square s2 = new TurboSquare(4);
		Rectangle r = new Rectangle(3, 5);

By the way, if we now remove the "set..." methods, we will have objects that can have their properties specified when they are created, but cannot be changed later. Such objects are useful in some situations. And in other situations, objects which can be changed later are useful.

Parametric type

This is an advanced topic; we will now not learn to create these types, only to use them. A frequently used data type is a list. A list of what? It depends; sometimes we need a list of numbers, or a list of texts, and today we could use a list of geometric shapes. So we have a list type, and we need to specify a parameter, of what type it is a list.

In Java language, lists are implemented with a "List" interface. The parameter type is specified in the beaks after the type; a list of geometric shapes would thus be "List<Shape>". Type "List" is an interface, not a class, because lists can be technically implemented in a few different ways. Most frequently we use the "ArrayList" class. So we create a new list with a command:

List<Shape> list = new ArrayList<>();

We add items to the list using function "add". If we want to use the items in list, we can use a "for" statement. In case of list we can use a simplified form with a colon "for (Shape shape : list)". By the way, both types "List" and "ArrayList" are from the directory "java.util", so at the beginning of the program we have to write "import java.util.*;".

Here is the complete program (without the "TurboSquare" class). In the main program we create a list of geometric shapes, where we put a few squares and rectangles. Then we enumerate these shapes (note that in that part of program we do not make difference between squares and rectangles; they are just shapes) and add their areas. Then we write the total area.

// file Shape.java
interface Shape {
	public int area();
}

// file Square.java
class Square implements Shape {

	protected int side;

	public Square(int side) {
		this.side = side;
	}

	public int getSide() {
		return side;
	}

	public void setSide(int side) {
		this.side = side;
	}

	@Override
	public int area() {
		return side * side;
	}

} 

// file Rectangle.java
class Rectangle implements Shape {

	private int height;
	private int width;

	public Rectangle(int height, int width) {
		this.height = height;
		this.width = width;
	}

	public void setHeigh(int height) {
		this.height = height;
	}

	public void setWidth(int sirka) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public int getWidth() {
		return width;
	}

	@Override
	public int area() {
		return height * width;
	}

}

// file Example.java
import java.util.*;

public class Example {

	public static void main(String[] args) {
		List<Shape> list = new ArrayList<>();
		list.add(new Square(10));
		list.add(new Square(20));
		list.add(new Square(30));
		list.add(new Rectangle(1, 4));
		int sum = 0;
		for (Shape shape : list) {
			sum += shape.area();
		}
		System.out.println("Total area = " + sum);
	}

}

This article was rather complex, especially towards the end, but we have reviewed some basic tricks of object-oriented programming in Java, so in the following article we can do some real programming.

If anything was not clear, try to read it in a textbook or online, and if it does not help, send me an e-mail.

Some final remarks to object terminology: The word "type" means a set of data that work the same way. In Java language we have three kinds of types. First, so called primitive types e.g. "int". We write them with small letters and there is only nine of them in the whole language (void, boolean, char, byte, short, int, long, float, double). Second, classes. Third, interfaces. We write classes and interfaces with a first capital letter. A function beloning to an object is called "method"; a variable belonging to an object is called "member variable" or "instance variable". If some variable is shared by all objects of the same class, we call it "static variable" or "class variable".

viliam@bur.sk