Karel ++
A Gentle Introduction to the Art of Object-Oriented Programming

This Chapter has not been published. It is Copyright, Joseph Bergin.


8 Moving Beyond Robots to Objects

In this Chapter we discuss object-oriented programming and give some additional classes that you can use in your programs. Here we use real C++ syntax. The simulators do not support the extensions discussed here, though we present the ideas in the context of robot programs.

8.1 Objects

I'm going to tell you a story to help you think about objects and how we deal with them.

Objects are electronic things.

They are active things. They can do stuff and they can remember stuff. They can talk to other objects.

Therefore they are more like people and cats than like rocks and trees.

You will probably find it advantageous to think of objects as if they were real rather than electronic. Thinking of them like people is a pretty good metaphor.

Just like people they are active. Just like people we can make requests of them. Just like people they themselves control what they do.

Most objects, though they can be active, are pretty passive. They are only active when they are asked to do some specific thing.

Mostly what objects do is provide services to other objects. When one object is active, it might ask another object to perform a service--maybe asking it for a piece of information or to do some task.

When an object asks another for a service, the asker is taking a "client" role and the askee is taking the "server" role.

When a client asks a server to perform a service it waits (politely) until the server completes the request and then it resumes its own activity. This is most helpful when the server needs to return information to the client. It is also possible to arrange things so the client continues its activity instead of waiting, but this is less usual.

There are many kinds of objects in a "system". What kind of object a thing is determines what services it can provide. If you ask an object to do something that isn't appropriate, that is an error. In C++, the computer won't let that happen.

So far in the robot programming world, the only objects that we have seen are the various kinds of robots. We ask robots to do things (perform services) and they remember things (that they have beepers or not). Street corners are almost like objects, but in the C++ program that runs our robot programs they are a bit simpler. However, we can implicitly ask a street corner if it has any beepers. Beepers aren't objects at all, since we don't ask them for any services. They are much more primitive.

In the last chapter we did see one other kind of object, however. This was the die that was rolled to determine how long a Philosopher would eat or think.

For the most part in what we have done, the main task block has been our only client and the robots have been servers. However, sometimes we have had one robot ask another to do something. Then the first robot becomes a client of the second, which itself becomes a server for the first. A client server transaction is always initiated by the client. The server may return information to the client when performing the service. For example if robot Tony asks robot Lisa if it has anyBeepersInBeeperBag, Lisa will return a boolean value. Other services (void methods) don't return such information.

Since objects, including robots, are things they have names. We saw in Chapter 6 that we can have aliases for robots. The same is true for objects in general. The names we have been giving robots are called variables. This is because the object that they refer to can change as the program progresses. A variable of robot type, or in general, object type, can refer to any object of its type or any subtype. This means that a variable of type ur_Robot can actually refer to a Racer robot. You should carefully distinguish between the variable that refers to a robot and the robot itself. A variable is just a name or "handle" that we use to get access to a robot so that we can ask it to perform services. We saw an example in Chapter 7 in which we didn't need to send any messages to a robot so we didn't use any variable to refer to it. This was only possible since the robot ran in its own thread and executed its own run method.

In C++ variables can refer to other things besides objects. Some of these variables are not references to things, but hold the things themselves. For example, C++ has a type called int (short for integer) in which we can store integer (whole number) values.

int size = 20; 

Note that we created this integer and gave it a value without using new. A variable that isn't a reference to an object, such as size, can only hold a value of its own precise type. We can perform arithmetic on int variables as you might hope.

int bigger = size + 5;

The new variable bigger will have value 25.

While C++ does have a few things that are not objects, the interesting things, and the ones you can build are all objects.

8.2 Classes

In object-oriented languages like C++ and Karel++, classes are used to describe objects. A class describes a set of objects of the same kind: Racers or Sweepers, for example. A class describes what an object can do (its services) in its methods. A class can also describe what an object can remember, though we haven't seen this yet.

So far we can refer to a robot using a variable, but it doesn't remember any name for itself. We can correct this as follows.

#include <string>

class BrainyRobot: public Robot 
{  public:
	BrainyRobot
	: Robot(street, avenue, direction, beepers)
	(string name, int street, int avenue, int direction, int beepers)
	{	myName = name; // Remember the name.
	}

	string name() { return myName; }

   private:
	string myName; //A variable in which to remember the name.
 };

The constructor for this class has an additional parameter of type string. A string is a sequence of Characters, usually written in double quotes. "Karel the Robot" is a string. String is a C++ class, so it describes objects of type string. When we create a BrainyRobot we must give it a name. The name of the robot doesn't need to be the same as the name of the variable that we use to refer to it, however.

BrainyRobot kjr("Karel", 1, 1, North, 0);
The name is something that the robot remembers using its instance variable called myName. In the constructor there is a statement that gives this variable a value, namely the value of the name parameter. The robot will remember this value and we can ask any BrainyRobot for its name using the name method.

In C++ we can print out a string so that the person running the program can see it by sending a print message to a special object called cout. For example we could write out the name of the BrainyRobot referred to by kjr with:

cout << kjr.name();
Here the cout object is our server and we need to send it information about how to carry out its service. We send it a string object that we get from the robot kjr. Note that kjr is also a server here and we are asking it to perform its name service, which it does by returning the current value of its myName instance variable.

A good way to think about a class is that it is a factory for creating objects of that type. This is why we used the factory metaphor (Karel-Werke) in Chapter 1. Each instance that it creates has all of the methods and all of the instance variables within the object (robot) itself. The new operator is like a message to the factory to create an object, though the usual message (".") syntax isn't used for this. The additional information that we give when we create a new robot, such as the street and avenue numbers, are called parameters. We will learn more about parameters in this Chapter.

8.3 C++ strings

"Jane Smith" is a special kind of object, known to C++, called a string. A string is a sequence of characters. Like any other object, a string object performs services for us. The most important service is to remember the sequence of characters that make it up.

A string can tell us its length.

	int val = someWords.length();

A String can also return a new string to us that has all of its own characters together with those of another string.

	string more = someWords.append("  And women's, too. ");

Distinguish carefully between the variable (a name) and the object to which it refers. The name is not an object. It refers to an object.

The string class describes more than 30 services that a string can provide.

class string
{	... // stuff left out
	public:
	 int length()
	{	...
	}

	string append (string s)
	{	...
	}
	...
};

(Note that the actuall C++ definition of string is much more sophisticated than the avove.)

There is a shorthand in C++ for string concatenation. It is just the plus sign: +. We could have written the concat example above more simply as:

	string more = someWords + string("  And women's, too. ");
The same operator, +, is also used to add numeric values as you might expect.

We can print out an informative message about a BrainyRobot with something like:

string message = string("Hello, my name is ") + kjr.name();
if (kjr.anyBeepersInBeeperBag())
{	message = message + string(" some ");
}
else
{	message = message + string(" no ");
}
message = message + string(" beepers in my beeper bag.");
cout << message;

This might produce something like the following:

Hello, my name is Karel. And I have some beepers in my beeper bag.

As we said above, in C++, not everything is an Object. Numeric values like 5 aren't objects, since they don't have behavior and can't remember things. They just are. (Like rocks.) All of the complex things that a programmer can define are objects, however. C++ also has a large number of classes (like String) that come with the system, so you have lots of possibilities for using objects without building any classes. But usually, you want to build your own kind of objects to do interesting things.

8.4 Parameters for Robot Methods

So far, all of our constructors have had parameters, but none of our methods has. There is no reason for this distinction, however. Often, when we ask an object to perform a service, we need to send along additional information. This can be data of any type, including other objects.

Suppose we are building the Mason class of Chapter 3. It might be better to be able to tell the Mason how high to build the wall.

void buildWall(int height)
{	for(int i = 0; i < height; ++i)
	{	putBeeper();
		move();
	}
}

Inside the method, the robot will be able to use the parameter as an ordinary variable.

Now, if we have a Mason named Ken we can say

Ken.buildWall(8);

And if Ken has enough beepers and is in the right place when we send the message, we will get our wall eight beepers high.

Here is a task that would be difficult without parameters. Suppose that we have one robot inside a rectangular room. We could ask that robot to measure the room, determining its area. Suppose that we have another robot outside the room that is supposed to put down a number of beepers equal to the area of the room. We can have the second robot ask the first how big the room is, but to do this the second robot needs to know who to talk to. We have solved this kind of program before by defining the first robot inside the second, but there is another way.

RoomMeasurer: public Robot
{   public:
	void measureRoom(){…}
	int sizeOfRoom(){…}
    private:
	int size = 0;
};

This is the general form for the room measuring robot. Notice that we have given the size instance variable an initial value of zero, in case someone asks for the size of the room before measuring it.

class BeeperPutter : public Robot
{   public:
	void learnCollaborator(RoomMeasurer* r)
	{	mycollaborator = r;
	}
	void putBeepers()
	{	if(myCollaborator != NULL)
		{	int size = myCollaborator->sizeOfRoom();
			for (int i = 0; i < size; ++i)
			{	putBeeper();
			}
		}
	}
    private: 
	RoomMeasurer* myCollaborator = NULL;
};

To make this work, we need to create the two robots and then tell the BeeperPutter who its collaborator is.

RoomMeasurer Kristin(…);
BeeperPutter Sue(…);
Sue.learnCollaborator( & Kristin );
Kristin.measureRoom();
Sue.putBeepers();
Notice that the reason for wanting to send an object as a parameter is that the receiver needs to communicate with that object.

8.5 Other Classes

In Chapter 7 we saw a use of the Die class. Here is the file "Dice.h" in which it is defined. It introduces a few new concepts.

#ifndef __Dice__
#define __Dice__

// A single die.  It may have any number of faces, even
// a physically impossible number.  Randomize to get better
// random behavior.  Without randomizing you get repeatable
// results.  
class Die
{       public:
                Die(unsigned int faces = 6);
                Die (const Die &d);
                ~Die();
                Die & operator = (const Die &d);
        
                int roll();
                void randomize(int seed = 0);
        protected:
                unsigned int _faces;
                void setFaces(unsigned int faces);
};

#endif

And here is the file "Dice.cpp" in which the functions are defined.

#include <stdlib.h>
#include <time.h>
#include "Dice.h" 

Die::Die(unsigned int faces):_faces(faces){}

Die::Die (const Die &d):_faces(d._faces){}

Die::~Die()
{       // nothing
}

Die & Die::operator = (const Die &d)
{       _faces = d._faces;
        return *this;
}


int Die::roll(){ return rand() % _faces + 1; }

void Die::randomize(int seed)
{       if (seed == 0)
        {       time_t now = time(NULL);
                srand(now % 32763);
        }
        else
                srand(seed);
}

void Die::setFaces(unsigned int faces){ _faces = faces; }

The Die class also needs some information from other files. In particular, it needs the random function from the stdlib.h and the time functions from time.h. To get access to a class defined in a different file we can include the header as shown here. Likewise, to use the Die class from we should say #incude "Dice.h"; We also need to link to Dice.cpp.

The Die class has one member variable. The variable _faces, is the number of faces of the Die. In our roll method we ask it to generate a random int. We then divide this by the value (number of faces) and retain only the remainder of the division with the % operator. The division operator itself is the slash character, /, by the way, and multiplication is the asterisk: *. Since a remainder is zero or more, but less than the divisor, we then add one to get a value in the range 1…value.

The C++ system comes with many many files, hundreds of classes and thousands of methods. It is a very extensive library.

8.6 Still More C++

So far, our robots remember if they have any beepers, but they don't remember or have a simple way to know how many they have. We can correct this by building a class that remembers how many it was given when it was created and changes this value whenever it picks or puts a beeper. We will modify BrainyRobot to show how to do this.

class BrainyRobot: public Robot // new version
{   public:
	 BrainyRobot
	:Robot(street, avenue, direction, beepers) 
	(String name, int street, int avenue, int direction, int beepers)
	{	myName = name;
		myBeepers = beepers;
	}

	void pickBeeper()
	{	super.pickBeeper()
		beepers = beepers + 1;
	}

	void putBeeper()
	{	super. putBeeper ()
		beepers = beepers - 1;
	}

	int beepers() { return myBeepers; }

	String name() { return myName; }

    private:
	String myName; 
	int myBeepers;
};

Now each BrainyRobot has its own name and it also keeps track of the number of beepers in its own beeper bag. We therefore give the class a new method so that we can ask a BrainyRobot how many beepers it has. We can use this new service in a variety of ways. We have already seen that we can print out information and we could print this out also.

cout << "The robot has " << kjr.beepers() << " beepers."<< endl;
However, we can do much more interesting things with such methods. Notice that the beepers method returns a value, just as our predicate methods do, though of different type. Suppose our robot kjr is on a corner with lots of beepers. We could say something like:
while (kjr.beepers() < 10)
{	kjr.pickBeeper();
}

If there are enough beepers on the corner, kjr will end this fragment with at least 10 beepers. This works because the expression kjr.beepers() < 10 has a boolean value, which is all that is required by if and while statements. Of course, The above statement isn't safe, since we don't know how many beepers there are on the corner. We could improve it as follows:

while (kjr.beepers() < 10)
{	if(kjr.nextToABeeper())
	{	kjr.pickBeeper();
	}
}

There is a shorthand for this however. It is the && (and) operator.

while (kjr.beepers() < 10 && kjr.nextToABeeper())
{	kjr.pickBeeper();
}

Here we have made two tests in the same while statement and connected them with and. If the first test fails ( kjr has 10 or more beepers already) then we don't execute the body. If it succeeds we do the second test. We only execute the body if the second test also succeeds. There is a similar || (or) operator. The and and or operators always work on boolean values. C++ also has comparison operators for the simple data (like int) These are <, >, <=, >=, !=, and ==. The last two are "not equal to" and "is equal to" respectively.

8.7 Problem Set

1. Write and test a new class, OdometerRobot, which keeps track of how far it has moved since it was created and can tell us how far that is.