Computer Science Canada

Classes Part III - Inheritance and Polymorphism

Author:  Cervantes [ Tue Jan 10, 2006 10:51 pm ]
Post subject:  Classes Part III - Inheritance and Polymorphism

Classes Part III:

Inheritance and Polymorphism


At the end of this tutorial, you will find an attached .zip. This file contains pretty much all the code we are using in this tutorial, and it is separated into stages, so that I'm not throwing the final code at you all at once.

An Introduction

    We've been doing a lot of class-writing lately, and you may or may not like it. (Be sure to have read Part I and Part II before reading this.) Regardless, no programmer should like to repeat oneself. If you write a Sword class in an RPG you are making, would you want to essentially rewrite that class to make a Crossbow class? Since both are Weapons, they share a lot of the same properties. It makes sense, then, that we should determine what properties they have in common and extract them into a place where both the Sword class and the Crossbow class can access them. Accomplishing this is one of the primary goals of inheritance.


The Fundamentals of Inheritance

    The basic concept of inheritance is that one class inherits things from another class. Data and methods on that data (the definition of a class/object) are what is inherited. In the example from the introduction, both the Sword class and Bow class would inherit from the Weapon class.

    The class that inherits another class is called the child class, whereas the class that gives its data and methods to the child classes is called the parent class. For example, the Sword and Bow classes are child classes, while the Weapon class is the parent class.

    Turing supports single-inheritance, but not multiple-inheritance. That is, a child class in Turing can only have one parent. Note that this does not prevent a parent class from having multiple children classes.

    Recall from the first tutorial that import and export come at the start of the class. So too does inherit, and inherit comes before both import and export.


The Appearance of 'Inherit'

    It's example time, and we'll build our example from scratch. We will make a Weapon class, then create Sword and Bow classes from it.

    Stage I - The Weapon Class
    We're not going to make a very complex Weapon class, because that's not the point of this exercise. The Weapon class will have an initialize method, to set all the data, such as damage, range, weapon name, etc. We'll set this data to generic values (this RPG is starting out small: there is only one type of weapon). We'll also give it a display procedure, to show us information on the Weapon.

    Here is the Weapon class and a demonstration of its use.
    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        proc initialize
            name := "Weapon"
            description := "Grasp weapon in chosen hand and use to smite thine enemy."
            weight := 5
            cost := 30
            damage := 8
            range := 0      % This is a melee weapon
        end initialize

        proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    var my_sword : pointer to Weapon
    new Weapon, my_sword

    Weapon (my_sword).initialize

    Weapon (my_sword).display

    Nothing fancy, here.

    Stage II - The Sword and Bow Classes Inherit Weapon
    Next, we create the Sword and Bow classes.
    code:

    class Sword
        inherit Weapon
    end Sword

    class Bow
        inherit Weapon
    end Bow

    You can place this code immediately after the Weapon class, so our code now looks like this:
    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        proc initialize
            name := "Weapon"
            description := "Grasp weapon in chosen hand and use to smite thine enemy."
            weight := 5
            cost := 30
            damage := 8
            range := 0      % This is a melee weapon
        end initialize

        proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    class Sword
        inherit Weapon
    end Sword

    class Bow
        inherit Weapon
    end Bow

    We could also give each class its own file, which we will look into later.

    The Sword and Bow classes have inherited the Weapon class. They have gained access to all the data and methods that the Weapon class had. It's almost as if we copied and pasted the code in the Weapon class into the Sword and Bow classes. However, we avoided the atrocity of copy & paste. Let's see if it actually works.

    We'll test just the Sword (because the Bow behaves identically, at the moment). Add the following code after the Bow class.
    code:

    var my_sword : pointer to Sword
    new Sword, my_sword

    Sword (my_sword).initialize

    Sword (my_sword).display

    Run it, and it should output the following:
    Output wrote:

    Weapon
    Grasp weapon in chosen hand and use to smite thine enemy.
    Weight: 5
    Cost: 30
    Damage: 8
    Range: 0

    This does not describe a Sword, but we had no reason to expect it to. We did not nothing to the Sword class, other than having it inherit the Weapon class. Thus, the child class did indeed inherit all the data and methods of the parent class; the Sword class did indeed inherit all the data and methods of the Weapon class. However, merely making copies of classes gets us no where. We have to be able to modify the child classes in some way.


Polymorphism

    Polymowhat? Poly means "many". Morph means "transform". Polymorphism means "many transforms"; it refers to the ability to have many forms of the same underlying thing. In this case, the "underlying thing" is a method.

    Stage III - Modifying the Sword Class
    We want to give the Sword a better description then "Grasp weapon in chosen hand and use to smite thine enemy." We also want to change the other data about it. To accomplish this, we will change the initialize method. But initialize was defined in the Weapon class. If the Sword class has gained all the data and methods of the Weapon class by inheriting it (which it did), then the Sword class already has an initialize method. And we all know we can't define a method twice. So how do we transform the initialize procedure?

    The answer comes in the keyword deferred. We are going to defer our procedure, then resolve it in the Sword class. To resolve a method means to give it a new definition in the child classes. We will also resolve the initialize method in the Weapon class, so that the Weapon class still works on its own.

    The syntax for deferring a procedure is very similar to the syntax for forwarding a procedure or function (used when a method calls another method, which in turn calls the first method: mutual recursion). The only difference is that instead of using the keyword forward, we use the keyword deferred. We resolve the methods by placing the keyword body before the procedure definition, just as if we were using forward to solve problems of mutual recursion.

    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        deferred proc initialize
        body proc initialize
            name := "Weapon"
            description := "Grasp weapon in chosen hand and use to smite thine enemy."
            weight := 5
            cost := 30
            damage := 8
            range := 0      % This is a melee weapon
        end initialize

        proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    class Sword
        inherit Weapon

        body proc initialize
            name := "Sword"
            description := "Swords are metallic, double-bladed weapons that vary in length and size.  They can be wielded in one or two hands."
            weight := 4
            cost := 15
            damage := 8
            range := 0
        end initialize
    end Sword

    class Bow
        inherit Weapon

        body proc initialize
            name := "Bow"
            description := "Bows are made of flexible wood with a string tied to either end.  They are used to smite enemies from afar, projecting arrows with great velocity and long distances."
            weight := 1
            cost := 10
            damage := 6
            range := 9
        end initialize
    end Bow

    var my_sword : pointer to Sword
    new Sword, my_sword
    Sword (my_sword).initialize

    var my_bow : pointer to Bow
    new Bow, my_bow
    Bow (my_bow).initialize

    Sword (my_sword).display
    put ""
    Bow (my_bow).display

    Output wrote:

    Sword
    Swords are metallic, double-bladed weapons that vary in length and size. They can be wielded in one or two hands.
    Weight: 4
    Cost: 15
    Damage: 8
    Range: 0

    Bow
    Bows are made of flexible wood with a string tied to either end. They are used to smite enemies from afar, projecting arrows with great velocity and long distances.
    Weight: 1
    Cost: 10
    Damage: 6
    Range: 9

    As you can see, calling the initialize method for the Sword or Bow objects calls the new version of it, which we created in the Sword and Bow classes, rather than the one created in the Weapon class. The initialize method of the Weapon class has been transformed into a new method, more suitable to a Sword or Bow.

    Note that exported methods are automatically deferred (and resolved in the same class, of course. Otherwise we wouldn't be able to call them). We could have just as easily written the same code, except removed the deferred line and removed the body in front of the initialize declaration. I encourage you to defer the procedure, anyways, since it makes the general outline of the class/objects clearer, and since you may decide later that you don't want to export that particular method.


Problem I - Parameters of Deferred Methods

    We cannot change the parameters given to the method we are transforming. Consider the initialize method of the Weapon class. What if we wanted more flexibility? What if we wanted to be able to set the value of name, description and so on by passing in values as parameters? Easy enough:
    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        proc initialize (name_, description_ : string, weight_, cost_, damage_, range_ : int)
            name := name_
            description := description_
            weight := weight_
            cost := cost_
            damage := damage_
            range := range_
        end initialize

        proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    var my_weapon : pointer to Weapon
    new Weapon, my_weapon

    Weapon (my_weapon).initialize ("Cervantes' Scimitar", "This scimitar is a fearsome weapon, indeed.", 3, 50, 6, 0)
    Weapon (my_weapon).display

    Now, let's create a Sword class that inherits the Weapon class, and try to redefine the initialize method. All swords are melee weapons, so we won't allow the range to be altered.
    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        deferred proc initialize (name_, description_ : string, weight_, cost_, damage_, range_ : int)
        body proc initialize        % The parameters used are the ones defined in the 'deferred' line.  We could rewrite them here, but we'd have to make sure they are exactly correct
            name := name_
            description := description_
            weight := weight_
            cost := cost_
            damage := damage_
            range := range_
        end initialize

        proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    class Sword
        inherit Weapon

        body proc initialize (name_, description_ : string, weight_, cost_, damage_ : int)
            name := name_
            description := description_
            weight := weight_
            cost := cost_
            damage := damage_
            range := 0
        end initialize

    end Sword

    var my_sword : pointer to Sword
    new Sword, my_sword

    Sword (my_sword).initialize ("Cervantes' Scimitar", "This scimitar is a fearsome weapon, indeed.", 3, 50, 6)
    Sword (my_sword).display

    You'll notice this code produces one error and one warning. The warning is that the initialize method of the Sword class has the incorrect number of parameters. It has to have the exact same parameters as were defined in the deferred line back in the Weapon class. The error is produced when we call the initialize method: we don't have the correct number of parameters, it tell's us.

    The only way to get around this is to accept it. You take the same parameters; you just ignore the ones you don't like. In calling the method, you pass whatever you like into those parameters (though they still must be of the correct type, of course).


Problem II - Completely Redefining Methods with no Assistance

    We must fully redefine the method, without the ability to call the version of the method that the parent class has. Say we wanted to change the display method of the sword; instead of outputting 0 for the range, we want it to output "Melee". We have to re-code it entirely, like this:
    code:

    class Weapon
        export initialize, display

        var name, description : string
        var weight, cost, damage, range : int

        deferred proc initialize
        body proc initialize
            name := "Weapon"
            description := "Grasp weapon in chosen hand and use to smite thine enemy."
            weight := 5
            cost := 30
            damage := 8
            range := 0      % This is a melee weapon
        end initialize

        deferred proc display
        body proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  ", range
        end display

    end Weapon

    class Sword
        inherit Weapon

        body proc initialize
            name := "Sword"
            description := "Swords are metallic, double-bladed weapons that vary in length and size.  They can be wielded in one or two hands."
            weight := 4
            cost := 15
            damage := 8
            range := 0
        end initialize

        body proc display
            put name
            put description
            put "Weight: ", weight
            put "Cost:   ", cost
            put "Damage: ", damage
            put "Range:  Melee"
        end display
    end Sword

    var my_sword : pointer to Sword
    new Sword, my_sword
    Sword (my_sword).initialize
    Sword (my_sword).display

    If this were Ruby, for example, I would use super to call the display method of the parent class (Weapon), which has already done most of the work for me. But to really do this in Ruby, I would have to have the method return an array of data, pop off the last element (the "range" element), and replace it with my own version, "Range: Melee".


An Example of Problems I and II

    Let's look at another example--one that involves the initialize method, and will combine the first and second problems. In discussing Problem I, we wanted to change the initialize method of the Sword class so that range was zero, no matter what. The name, description, weight, cost, and damage are still going to be passed in as parameters, however. Thus, the first five lines of the initialize method are the same in both the Weapon class and the Sword class. They both look like this:
    code:

            name := name_
            description := description_
            weight := weight_
            cost := cost_
            damage := damage_

    What's the point in repeating this code in both methods? It would be easier to code it once in the Weapon class. If these two problems discussed above didn't exist in Turing, the initialize method of the Sword class would take 5 parameters, and it would call the initialize method of the Weapon class, passing it (name_, description_, weight_, cost_, damage_, 0), and that would be that. Zero is passed as the last parameter to the Weapon's initialize method; range is set to 0; there is no repeat of code. But alas, Turing has no equivalent of Ruby's "super".

Author:  Cervantes [ Tue Jan 10, 2006 10:54 pm ]
Post subject: 

Expansion to Multiple Files

    Earlier I mentioned that we could give the Sword and Bow class their own files. The advantage to doing this is merely that it keeps our code tidy, and it prevents us from having to search for the end of a class and the beginning of another.

    To do this, we have to make each class into a Turing Unit (see step 5 of the modules tutorial). Just put the keyword unit at the start of the class, and ensure you only have one class per file. Save each file as <className>.tu. For example, I would save the Weapon unit as Weapon.tu.

    Stage IV - Expanding to Multiple Files
    You should have three .tu files, all in the same folder. You don't have to do anything special to link the files; the inherit Weapon line will do it all for you. (If you haven't looked at the .zip file at the end of this post, this would be the ideal time to do so.)

    Next, you need a file to run. This will contain the code that creates the objects, initializes them, and displays them. Create a "Run.t" file (in the same folder as your Turing Units) and give it this code:
    code:

    import Sword, Bow

    var my_sword : pointer to Sword
    new Sword, my_sword
    Sword (my_sword).initialize

    var my_bow : pointer to Bow
    new Bow, my_bow
    Bow (my_bow).initialize

    Sword (my_sword).display
    put ""
    Bow (my_bow).display

    This is the code we were using previously, except that we had to import the Sword and Bow classes. Note that we did not import Weapon, since we don't directly create any Weapon objects in this code. Also, note that just importing Weapon will not give you access to Sword or Bow.


Problem III - Importing Many Units

    We must import each unit manually. If expanded our Weapon system to include class definitions for 50 types of Weapons, we would need to import 50 units into our "Run.t" program. There is no way to make a package of classes.


Object Relationships: "is a"

    The inherit clause does more than give the child class access to the data and methods of the parent class. Inheritance creates a relationship between child and parent: between Sword and Weapon, and between Bow and Weapon. It creates a parent-child relationship. It creates an "is a" relationship. Sword "is a" Weapon. Bow "is a" Weapon. Let's withdraw for a moment, and say this in even more English style terms: Both Swords and Bows are types of Weapons. Wherever a Weapon can go, a Sword or a Bow can go.

    Stage V - A Player Character Equips a Weapon
    Consider the RPG we are building this for. You want to be able to equip a Weapon--to put a Weapon in your hand to beat the Monsters with. You may wish to wield a Sword, engaging in melee combat; or you may wish to wield a Bow, picking the Goblins off from afar. You should be able to equip either a Sword or a Bow, but we don't want to have to code that into the equip_weapon procedure (this procedure may be found in the PlayerCharacter class, which could inherit the Character class, which could inherit the Object class, perhaps). Instead, we use the "is a" relationship. Since a Sword "is a" weapon, a Sword can go anywhere a Weapon can. Thus, in attempting to equip a Weapon to bash Monsters with, we expect to receive a Weapon object. A Sword object is a Weapon object too, so we can easily give the equip_weapon procedure a Sword object, though it expects a Weapon object. Let's code this.

    We'll make a PlayerCharacter class, and it will have initialize, equip_weapon, and display procedures. The initialize and display procedures will be quite simple. The equip_weapon procedure will take a parameter which is a Weapon object, and store it in the variable, weapon. weapon is a pointer to the Weapon class (make sure you had a good understanding of Classes: Part II for this part).

    We will initially set the value of the weapon variable to nil: it will initially be a nil pointer, not pointing to any particular class, not referencing any object. The variable type, however, is still pointer to Weapon.

    As a final note, since we are working with Weapon objects, we have to import the Weapon class into the PlayerCharacter class. Since we are using units, this is as simple as putting import Weapon at the start of the PlayerCharacter class, and making certain that both Turing Units are in the same directory.

    Here is the PlayerCharacter unit:
    code:

    unit
    class PlayerCharacter
        import Weapon
        export initialize, equip_weapon, display

        var name, race, klass : string
        var weapon : pointer to Weapon

        proc initialize (name_, race_, klass_ : string)
            name := name_
            race := race_
            klass := klass_
            weapon := nil
        end initialize

        proc equip_weapon (weapon_ : pointer to Weapon)
            weapon := weapon_
        end equip_weapon

        proc display
            put "Name:   ", name
            put "Race:   ", race
            put "Class:  ", klass
            put "Weapon: " ..
            if weapon = nil then
                put "None"
            else
                put Weapon (weapon).name
            end if
        end display

    end PlayerCharacter


    We had to make a slight modification to the Weapon class for this to work: we had to export the name variable. The export line of the Weapon class now looks like this:
    code:

    export initialize, display, name


    And finally, the code to create a PlayerCharacter object and equip him with a Weapon. We will create Minsc, the human ranger, and he will wield a Sword.
    code:

    import Weapon, Sword, Bow, PlayerCharacter

    var minsc : pointer to PlayerCharacter
    new PlayerCharacter, minsc

    PlayerCharacter (minsc).initialize ("Minsc", "Human", "Ranger")

    var minsc_sword : pointer to Sword
    new Sword, minsc_sword
    Sword (minsc_sword).initialize

    PlayerCharacter (minsc).equip_weapon (minsc_sword)

    PlayerCharacter (minsc).display


    Now, let's examine this a bit. minsc_sword is a Sword object. A Sword "is a" Weapon. The equip_weapon procedure of the PlayerCharacter class asks for a Weapon object as its parameter. But we gave equip_weapon a Sword object. Since a Sword "is a" Weapon, a Sword can go anywhere a Weapon can. A Sword can be passed to equip_weapon because of this relationship.


Problem IV - Anonymous Objects

    In order to create an object, we must go through the process of making a pointer to the class, then creating a new instance of the class, referenced to the pointer. There is no other way. Thus, to give Minsc a Weapon, we had to create a Sword object, then pass that Sword object to the equip_weapon procedure. But what do I need minsc_sword in my main program for? I don't: The data of minsc_sword's existence is stored within the minsc object.

    Things would be easier if I could create anonymous objects: objects without a name. If this were Ruby, the code would look roughly like this:
    Ruby:

    minsc = PlayerCharacter.new( "Minsc", "Human", "Ranger" )
    minsc.equip_weapon( Sword.new )

    Sword.new creates an anonymous Sword object, an object without a name (I didn't do minsc_sword = Sword.new). This saves us from creating a Sword object in the global namespace and then passing it to equip_weapon.


Creating an Inventory - More of "is a"

    In Part I of this tutorial, we discussed the following scenario: creating a pointer to ClassName, then creating a new instance of ClassName, linked to pointer_name. In code, it looks like this:
    code:

    var my_sword : pointer to Sword
    new Sword, my_sword

    Does it seem repetitive? It does, but it gives us flexibility: The class in the first line does not have to match the class in the second line. We'll see why soon.

    Stage VI - Creating an Inventory
    An inventory is nothing more than an array of objects that the PlayerCharacter object is holding on to. For our purposes, let's assume Weapons are the only type of Items in our game. (We could expand our Item system somewhat to include an Item class; Weapon, Armour, Scroll, Book, Boots, etc. could inherit Item. However, we don't need to do that to illustrate the current subject.)

    The inventory is an array of Weapon objects; it is an array of pointer to Weapon.
    code:

    var inventory : array 1 .. 10 of pointer to Weapon

    The inventory should be initialized each element being nil: pointing to nothing; referencing no object.

    We will give PlayerCharacter add_to_inventory and remove_from_inventory methods, and we'll make display show the contents of the inventory as well. Here's the code:
    code:

    unit
    class PlayerCharacter
        import Weapon
        export initialize, equip_weapon, add_to_inventory, remove_from_inventory, display

        var name, race, klass : string
        var weapon : pointer to Weapon

        % For the moment, assume Weapons are the only type of Items in the game
        var inventory : array 1 .. 10 of pointer to Weapon

        proc initialize (name_, race_, klass_ : string)
            name := name_
            race := race_
            klass := klass_
            weapon := nil

            for i : 1 .. upper (inventory)
                inventory (i) := nil
            end for
        end initialize

        proc equip_weapon (weapon_ : pointer to Weapon)
            weapon := weapon_
        end equip_weapon

        proc add_to_inventory (weapon_ : pointer to Weapon)
            % Iterate through each inventory slot, looking for an empty one
            for i : 1 .. upper (inventory)
                if inventory (i) = nil then
                    inventory (i) := weapon_
                    exit
                end if
            end for
        end add_to_inventory

        proc remove_from_inventory (i : int)
            inventory (i) := nil
        end remove_from_inventory

        proc display_inventory
            for i : 1 .. upper (inventory)
                put i, ": " ..
                if inventory (i) = nil then
                    put "Empty"
                else
                    put Weapon (inventory (i)).name
                end if
            end for
        end display_inventory

        proc display
            put "Name:   ", name
            put "Race:   ", race
            put "Class:  ", klass
            put "Weapon: " ..
            if weapon = nil then
                put "None"
            else
                put Weapon (weapon).name
            end if
            put "Inventory:"
            display_inventory
        end display

    end PlayerCharacter

    The "Run.t" code is the following:
    code:

    import Weapon, Sword, Bow, PlayerCharacter

    var minsc : pointer to PlayerCharacter
    new PlayerCharacter, minsc

    PlayerCharacter (minsc).initialize ("Minsc", "Human", "Ranger")

    var minsc_sword : pointer to Weapon
    new Sword, minsc_sword
    Sword (minsc_sword).initialize
    PlayerCharacter (minsc).equip_weapon (minsc_sword)

    var minsc_bow : pointer to Weapon
    new Bow, minsc_bow
    Bow (minsc_bow).initialize
    PlayerCharacter (minsc).add_to_inventory (minsc_bow)

    PlayerCharacter (minsc).display

    There is an important trick here. When creating a Weapon object such as minsc_sword, the pointer points to Weapon, but a new instance of Sword was created, linked to the minsc_sword pointer.

    To understand this, we have to use the "is a" relationship. Sword "is a" Weapon, so a Sword can go anywhere a Weapon can. This extends even to creating a Sword object linked to a pointer that points to the Weapon class.

    Why did we have to do this? The answer is that the inventory holds an array of Weapon objects--an array of pointers to Weapon. The data stored in the array must be of the data type, pointer to Weapon. But, we don't want to create Weapon objects all over the place; we want to use our Sword and Bow classes, because they make our life easier. Thus, we create instances of the Sword and Bow classes, with linked to pointers that point to the Weapon class.

    Stage VII - Expansion of the Item System
    There are no new concepts in Stage VII, so this section will be short. Essentially, Stage VII expanded the Item system so that a new Item class was the top-dog. Weapon inherits Item, Sword and Bow inherit Weapon. Other types of Items could be added, such as Armour, Boots, Helmets, etc.

    You can view the code for Stage VII in the .zip file, attached to the end of this post.



Questions (but no Answers)

    To compliment this tutorial, it is recommended that you write a program that involves inheritance and polymorphism. I suggest expanding the example we have built in this tutorial, though I recommend staying away from graphics. If you truly want to make an RPG, make it a text RPG. Begin by planning out all the objects you'll need (Item -> Weapon/Armour, Character -> PlayerCharacter/NonPlayerCharacter, Map -> Terrain, Object -> Door/Chest). Determine object relationships. Plan a Class Hierarchy.


A Conclusion

    Inheritance and polymorphism are powerful programming concepts. They are about reusing code that is common to different objects; it is about creating a logical, object-oriented-sensical structure; it is about creating relationships between objects, including the "is a" relationship.

    By now, we've had a good look at inheritance and polymorphism. You can see how Turing lacks functionality in this area. This is because Turing is not a strong object-oriented nature; rather, object-orientedness was thrown in as an afterthought, and it was left in a haggard state. There are so many basic aspects of inheritance and polymorphism that Turing gets wrong that it gives a strong push to learn a more object-oriented programming language. It is an interesting thought that the last important part of Turing to learn is also the one to push the programmer away from the Turing language the most. Despite this, inheritance and polymorphism are still powerful programming concepts, even in Turing. Learn to love them!

Author:  TokenHerbz [ Tue Apr 04, 2006 5:55 pm ]
Post subject: 

OMG, Cervantes, your crazy to have so much knowlend in CLASS's

i read your 3 class tuts a few times, and still don't know how to use them well :S

Author:  MysticVegeta [ Tue Apr 04, 2006 5:58 pm ]
Post subject: 

Read part 1 a few times, then you will undersatnd whats going on in Part 2 and 3

Author:  TokenHerbz [ Wed Apr 05, 2006 2:34 pm ]
Post subject: 

Mistic i did, as i said:

I read your 3 tuts (class1, class2, class3), a few times (each)...

Sorry to confuse you :S

Author:  MysticVegeta [ Wed Apr 05, 2006 5:18 pm ]
Post subject: 

Yes I know thats why I said "Read Part 1 a few times", "then", "read 2 and 3" So read Part 1 a few times before even you glance at part 2, cause I remember reading them a long time ago so I had to read part 2 a few times cause I already knew the content of part 1.

Author:  Cervantes [ Wed Apr 05, 2006 7:18 pm ]
Post subject: 

I wouldn't recommend reading the part I tutorial (read: any tutorial) a few times. By which I mean the following: Read it once, well. Go through it slowly enough that you understand everything. Just reading it, even if you read it ten times, will not help you understand it if you're reading too fast and thinking not enough.

This tutorial explains it all, but there is no substitute to practice. Really, make something with classes. Something not too hard, like a Button class. Once you can make a single class and make objects from it, move on to the next part of the tutorial.
Make something for part II. An easy thing to make for part II is some class that has a method that accepts as a parameter another object of the same class. For example, a Vector class. It defines a function, add, that takes another Vector object as a parameter, adds the two vectors, then returns a new Vector object that is the resultant of the two given vectors (one vector was passed in as a parameter; one you called the add function on).

Author:  md [ Wed Apr 05, 2006 7:25 pm ]
Post subject: 

Less pointless debate about how inept each of you is, and more praise for Cervantes! Congrats on putting together such a wonderful tutorial on a subject so rarely used (and poorly implemented) in turing.

Author:  Cervantes [ Wed Apr 05, 2006 7:45 pm ]
Post subject: 

Thanks! Smile

Author:  ericfourfour [ Sat Dec 09, 2006 11:29 pm ]
Post subject: 

Sorry to necro-post but I found a very useful concept using classes in Turing by using the implement and implement by command.

I'm sure you've known about:
c++:
class Foo;

class Bar
{
    Foo* foo;
};

class Foo
{
    Bar* bar;
};

in c++. I don't know what it is called but I know it is very useful. Did you know however you could do that in Turing?

In Turing you can set it up like this:
code:
class Foo
    implement by FooBody
end Foo

class Bar
    import Foo

    var foo : ^Foo
end Bar

class FooBody
    implement Foo
    import Bar

    var bar : ^Bar
end FooBody

There is more and I can expand on it if you want. The only thing you have to watch out for is that Foo can inherit but FooBody cannot. They also must be in different Turing Unit files.

Author:  wtd [ Sat Dec 09, 2006 11:45 pm ]
Post subject: 

ericfourfour wrote:
Sorry to necro-post but I found a very useful concept using classes in Turing by using the implement and implement by command.

I'm sure you've known about:
c++:
class Foo;

class Bar
{
    Foo* foo;
};

class Foo
{
    Bar* bar;
};


This is simply forward declaration of a class.

Author:  CodeMonkey2000 [ Wed Dec 13, 2006 5:36 pm ]
Post subject: 

theres something wrong, with my long bow class, if i want to specify that the type of arrow you use affects the damage and rang, how would i use polymorphisism for it?
code:

class Weapon
    export initialize, display

    var name, description : string
    var weight, cost, damage, range : int

    deferred proc initialize
    body proc initialize
        name := "Weapon"
        description := "Grasp weapon in chosen hand and use to smite thine enemy."
        weight := 5
        cost := 30
        damage := 8
        range := 0      % This is a melee weapon
    end initialize

    proc display
        put name
        put description
        put "Weight: ", weight
        put "Cost:   ", cost
        put "Damage: ", damage
        put "Range:  ", range
    end display

end Weapon

class Sword
    inherit Weapon

    body proc initialize
        name := "Sword"
        description := "Swords are metallic, double-bladed weapons that vary in length and size.  They can be wielded in one or two hands."
        weight := 4
        cost := 15
        damage := 8
        range := 0
    end initialize
end Sword

class Bow
    inherit Weapon

    body proc initialize
        name := "Bow"
        description := "Bows are made of flexible wood with a string tied to either end.  They are used to smite enemies from afar, projecting arrows with great velocity and long distances."
        weight := 1
        cost := 10
        damage := 6
        range := 9
    end initialize
end Bow

class LongArrow
    inherit Weapon

    body proc initialize
        name := "Long Arrow"
        description := "Increses the power of your longbow."
        weight := 1
        cost := 1
        damage := 2
        range := 0
    end initialize
end LongArrow

class LongBow
    inherit Bow, LongArrow

    body proc initialize
        name := "Long Bow"
        description := "The long bow is a better verson of your old bow."
        weight := 1
        cost := 15
        damage := 10
        range := 15
    end initialize
end LongBow

var my_sword : pointer to Sword
new Sword, my_sword
Sword (my_sword).initialize

var my_bow : pointer to Bow
new Bow, my_bow
Bow (my_bow).initialize

Sword (my_sword).display
put ""
Bow (my_bow).display

var my_long_bow : pointer to LongBow
new LongBow, my_long_bow
LongBow (my_long_bow).initialize
put ""
Bow (my_long_bow).display

Author:  Clayton [ Wed Dec 13, 2006 8:24 pm ]
Post subject: 

Try posting in [Turing Help] next time Razz

But anyways... You would have to have some sort of pointer in your longbow class that keeps track of the type of ammunition you use in your bow. Then, have some sort of function in the parent class of your bow ammunition have a way to calculate the damage (bonus) that it would have to your bows power.

Author:  Cervantes [ Thu Dec 14, 2006 12:55 am ]
Post subject: 

spearmonkey2000 wrote:

code:

class LongBow
    inherit Bow, LongArrow
    ...


Turing only supports single inheritance, not multi inheritance. So your LongBow can't inherit both from Bow and LongArrow. But then again, why should it inherit from LongArrow? Look at the "is a" relationship. Is a LongBow a LongArrow? Certainly not.

I'd recommend making an Ammunition class. From that you can make classes for Arrows, Bolts, ThrowingDaggers, what have you... A Bow object can then have a method called fire that takes in an Arrow object, possibly among other things. You could then create child classes from Arrow, such as FireArrow or PiercingArrow.

Author:  CodeMonkey2000 [ Thu Dec 14, 2006 6:47 pm ]
Post subject: 

so u cant morph 2 parents classes? then wat do u do when u are in that senerio? are you forced to copy and paste? or is there another way around this?

Author:  Cervantes [ Thu Dec 14, 2006 9:43 pm ]
Post subject: 

That's correct. You cannot have a class inherit from two parent classes. In Turing, our objects are asexual. Ruby is similar, in that it only supports single inheritance, but in Ruby we have mixins. Basically, it allows us to import code from modules into a class. modules in Ruby can have module/class variables/methods as well as instance variables/methods, so importing a module into a class makes sense, unlike in Turing, where classes only have instance variables/methods and modules only have module variables/methods.

I ask you, do you really need to use multiple inheritance?


: