Objects, Inheritance and Polymorphism


Object Semantics

Suppose we have a simple class Foo:

class Foo {
    private int theValue;
    public Foo(int v) {theValue = v;}
    public int getValue() {return theValue;}
    public void setValue(int v) {theValue = v;}
}

Foo x = new Foo(3);
Foo y = new Foo(5);
x.setValue(2);        // x is now 2
x = y;                // x is now 5
x.setValue(3);        // x and y are now 3. Why does y change?

It is important to understand why y changes at the end. The local variables x and y do not represent two different locations for the Foo objects; instead, they represent pointers to the objects. After the line x=y, x and y point to the same object.



Polymorphism

Here is a fundamental example about classes and so-called inheritance, also in the file poly.cs. The Child class "extends" the class Base. However, a Child object permanently retains its identity, even when it is assigned to a Base variable (as in b2 = new Child(4,5)). When the get() method is called, it is the Child get method, not the Base get method.

using System;
using System.IO;

class Demo {
    static void Main(string[] args) {
	Base b1 = new Base(3);
	Base b2 = new Child(4,5);
	Console.WriteLine("b1.get() = {0}, b2.get = {1}", b1.get(), b2.get());
    }

}

class Base {
    private int x;
    public virtual int get() {return x;}
    public Base(int X) {x = X;}
}

class Child : Base {
    private int y;
    public override int get() {return base.get()+y;}
    public Child (int X, int Y) : base(X) {y=Y;}
}

b1.get() returns 3; there is not much else it could return. However, b2.get() returns 9. An observer who reasoned that b2 was of type Base might try to invoke Base.get(), which would return 4.

The "polymorphism" behavior depends on get being declared virtual in class Base, and then override in class Child. If we leave out the override, then the call to b2.get() uses "static" typing: b2 has type Base, so Base.get is called, and the result is 4, not 9.

ToString

Console.WriteLine is polymorphic: when you give it a non-string type, it calls the object's ToString() method to convert it to a string. We saw this in the ratio.cs demo.

Ratio.ToString can be called explicitly whenever needed, as r.ToString(). However, note that ToString() works more generally, using inheritance. The master parent class Object defines ToString(); any subclass can override that definition, as is being done here. In WriteLine(), when an Object needs to be printed then ToString() is called implicitly; the rules of inheritance ensure that the most specific version of ToString is the one to be invoked.

Shapes

One classic application of this is with drawing shapes. There is a base class Shape, and then child classes for each specific shape: Triangle, Rectangle, Circle, etc. The base class has a draw() method, but it does nothing (in fact, it is usually labeled abstract, meaning it must be supplied by a subclass). If we have a list shapeList of Shape objects (that is, a picture!) we can draw it as follows:

    foreach (Shape s in shapeList)
        s.draw();

We do not have to check each shape s for what kind of shape it is.

Zuul

The Game of Zuul came from a Java implementation in Objects First with Java by David Barnes and Michael Kölling. Polymorphism is used extensively to create different puzzle-room classes that behave differently from the default Room class.

In the Game class, the main game loop is as follows:
        while (! finished) {
            Command command = parser.getCommand();
            finished = processCommand(command);
        }
The processCommand() function understands some universal commands such as "quit", "help", "inventory" (listing the things you have with you) and "look" (listing the things visible in the current room, although the room is able to influence the visibility). But everything else is processed by the room itself, via the Room.respond() method. A subclass of Room can in effect redefine respond(), and thus create new behavior.

Overview of basic classes by file

Game.cs: contains the main game loop, above

Parser.cs, Command.cs, commandWord.cs: defines the basic mechanism for reading commands

Item.cs and Light.cs: Items are things you can carry around with you, like swords and flashlights. Some Items have the special property that they can be "lighted"; some rooms (eg DarkRoom) won't show you whats' going on unless you are carrying a light.

Room.cs: Rooms have a description and a list of exits (north, south, east, west, up, down, though others are possible). Rooms also have the following virtual methods:

Each room contains a list of Items found in the room; Items are portable and can be TAKEn or DROPped.

RoomMaker.cs: creates the map of rooms. Technically this is called a directed graph. (This isn't "just" a directed graph, or digraph; the different links have names.)

Then there are the special rooms:

DarkRoom: if you don't have a light, only the way out is shown. No Items in the room are visible. (And you cannot TAKE them if you guess their name)

DustyRoom: if you SWEEP, you find a WAND

LockedRoom: One exit is locked, and you can only go that way if you have a key in your inventory. No "unlock" action is needed.

SerpentRoom: contains a giant but harmless serpent. He does block your way back, though.

ValveRoom: You can't go DOWN unless you first TURN VALVE

ZooRoom: contains a sad zookeeper looking for a very small serpent

Downcasting

The player carries around a list of Items. How do we check this list to see if one of the Items is a Light, and, if so, is actually Lighted? There is no method in the Item class for asking if it is a Light, let alone the light's status. Polymorphism, in other words, is no help to us. Polymorphism applies only to methods that are part of the base class (and then redefined by the derived class), not to methods (like Light.is_lighted()) that apply to the

The process of checking an object belonging to a base class (Item) to see if it is in fact a member of a derived class, and, if so, viewing it as such, is called downcasting.

The Light class has two static methods that do downcasting; here is getLight() that returns the first lighted Light object on a List<Item>:
    public static Light getLight(List<Item> inv) {
        int i = 0;
        foreach (Item itm in inv) {
            if (itm is Light) {
                Light lt = itm as Light;         // note (cast)
                if (lt.isOn()) return lt;
            }
        }
        return null;    // get here if not found
    }
Note the "if (itm is Light)" which is a test for whether an Item is a Light, and "Light lt = itm as Light", which is the actual conversion. If itm is not a Light, then lt becomes null; this conversion is thus safe (even though we've already checked here that itm is a Light, the compiler treats the two statements as independent).

Shapesworld

Suppose we have the Shape class from earlier, and we want to go through shapeList to find each Rectangle and modify it by rounding its corners with the Rectangle-specific method setRoundCorners(). We can do the following:

    foreach (Shape s in ShapeList) {
        Rectangle r = s as Rectangle;
        if (r!= null) r.setRoundCorners();
    }

If all we wanted to do was draw the rectangles, we could use this; we don't have to downcast as we're not actually calling any method that is not present for the Shape class:

    foreach (Shape s in ShapeList) {
        if (s is Rectangle) s.draw();
    }

Commands and double dispatch

I want "turn valve" to work only in the Valveroom. However, it is then important that "turn" be at least recognized everywhere, or else someone might erroneously conclude that "turn" isn't a game command at all. In other rooms, the response is "Hmm.. I don't seem to know how to turn here!" which is a clue that "turn" might work somewhere else.  How do I do this?

In the Room class, there is a catchall: if the verb word is legitimate (as determined by the CommandWords class), then the Room.response() is "Hmm ... I don't seem to know how to " + commandWord + "here!".

This means that each new verb must be entered into the CommandWords class.

It would also be possible to add to CommandWords a default response for each verb to be used in rooms that didn't "understand" that verb. In this case we might have a polymorphic verb.response() method; each verb would have its own class and would have its own response().

This is double dispatch: we want response() to depend on both the room and the verb, polymorphically. There are languages that allow this directly, but C# (and most OO languages) are a little more constrained. If we took that route, we would first try room.response(verb) and then, if that "failed", would try verb.response() (probably built into the default Room.response().