Absolute Newbie's Tutorial for Object Oriented Des

BlitzMax Forums/BlitzMax Tutorials/Absolute Newbie's Tutorial for Object Oriented Des

SurreaL(Posted 2005) [#1]
Absolute Newbie's Tutorial for Object Oriented Design

This article was written after doing some research into Object Oriented techniques, and the goal is to help enlighten others who aren't quite sure what to think of all this "OO" stuff. I decided to write it after a little talk on IRC, and after seeing some general confusion about OO there and on the forums as well.. I will attempt to relate this information to BlitzMax where applicable, but keep in mind the theories aren't really language-specific. Obviously this document will not be 'definitive', but I do hope at least helpful :) This will be mostly a conceptual article.. It will have only a little code, and the code which is there won't really do much, but is only there to illustrate a point. but now.. *rolls up sleeves* Lets get to work.

Object Oriented Programming - What is it?

The term Object Oriented Programming(or OOP, to save typing) can mean alot of things to alot of different people, but it generally comes down to being a design philosophy. It is, simply put, a way of thinking. BlitzMax has recently been released with some language structures in place to support an OO mindset. Other languages which have attempted OOP include Simula (the first OO language), c++ (which was an add-on to the procedural "c" language), and Java (often argued to be one of the 'best' implementations of OOP). To understand what OOP is, it is important to remember what we are attempting to do with computer languages in the first place.. and that can be termed Abstraction.

In other words, we as programmers are always trying to abstract (or relate) some sort of problem, to the realm of zero's and one's which a computer can understand and work with. There are various levels of abstraction, which can sort of loosely be tied to the different generations that programming languages fit in. Therefore, the lowest level of abstraction would be to write in machine code (which I doubt many people ever do, these days), whereas one step up from that would be writing ASM code, and so on. Object Oriented design is just another, higher, level of abstraction.

Up until now, if you haven't been using OOP, you've most likely been doing 'procedural' programming. In other words, you define somewhere in your program all the data you will need, and then through the course of execution, the code will make various calls to procedures (or sub-routines.. or functions) which will work with the data provided. Let us look at how an Object Oriented approach will handle this. These following characteristics were ripped out of the book "Thinking In C++"*, and describe points written by Alan Kay to summarize SmallTalk (a language often named predecessor to C++)

#1 Everything is an object. Think of an object as a fancy variable; it stores data, but you can “make requests” to that object, asking it to perform operations on itself. In theory, you can take any conceptual component in the problem you’re trying to solve (dogs, buildings, services, etc.) and represent it as an object in your program.
#2 A program is a bunch of objects telling each other what to do by sending messages. To make a request of an object, you “send a message” to that object. More concretely, you can think of a message as a request to call a function that belongs to a particular object.
#3 Each object has its own memory made up of other objects. Put another way, you create a new kind of object by making a package containing existing objects. Thus, you can build complexity in a program while hiding it behind the simplicity of objects.
#4 Every object has a type. Using the parlance, each object is an instance of a class, in which “class” is synonymous with “type.” The most important distinguishing characteristic of a class is “What messages can you send to it?”
#5 All objects of a particular type can receive the same messages. This is actually a loaded statement, as you will see later. Because an object of type “circle” is also an object of type “shape,” a circle is guaranteed to accept shape messages. This means you can write code that talks to shapes and automatically handles anything that fits the description of a shape. This substitutability is one of the most powerful concepts in OOP.

As he states: "each object is an instance of a class, in which “class” is synonymous with “type.”" This is exactly the case with BlitzMax. Every time you hear or read about Classes in other OO languages, they are the same thing as Types in BlitzMax. I will move back and forth between both terms because.. well.. I can.

Specifics of OO and what it means to BlitzMax

Back to the point of OO. The idea behind it was to allow the programmer to define their own data types (Classes, in c++/java, or just Types, in blitzmax). This meant that the programmer was no longer forced to try to express his problem in terms of the data types provided by the language (integers, strings, etc), but they were allowed to extend on that with their own complex data types that were internally just a combination of the simple data types. This lets the programmer worry about the problem they're trying to model, instead of the language they're trying to model it in. Hopefully this helps shed some light on the background of Object Oriented design.. now lets get to the gory details. Please refer to the following example code for the next couple of sections:
Type Person	'This code defines a Type and lets Blitz know what properties and code to assign to it.
	Field Name$, Height, Weight		'basic properties
	Method Breathe ()			'a simple method, specific to one type instance
		'Make the person breathe
	EndMethod
	Function Count ()			'a function, which ignores any particular type instances
		'Go through and count all the 'person' instances
	EndFunction
End Type

'This code shows setting up a couple types
person1:Person = New Person
person1.Name$ = "fred"
'And one more for good measure
person2:Person = New Person
person2.Name$ = "joe"
First, some terms. An Object can be thought of as an instance of a Type. In our example, 'person1' and 'person2' are an example (or instance) of a 'person', who have been assigned the properties "Fred" and "Joe" for the Name$ variable. Therefore "person1" is the object (specific instance), and 'Person' is the Type (general rules for any instances). This means that "Fred" has similar ways to describe him as "Joe" (whatever properties the type Person wishes to define.. height, weight, name, etc), but has his own instance of those properties. You'll notice along with properties (Fields), there are also ways to interact with a 'Person' type. On to..

Interfaces - Type Methods and Functions

A function is of course simply a collection of code which is grouped under a single name, to be called elsewhere in code. In OO terms however, a method is thought of as a function accessible to a particular class only (or Type). In other words, the 'Person' class in our example has a method called 'breathe', which would be something that any instances of that class could call. (ie 'joe' could breathe, just like 'fred' can.) In BlitzMax the only difference between a type method and a type function is that a method is called relative to a specific instance of a Type, and therefore has access to the fields of that particular Type. A type function (also known as a 'static method' in c++) is essentially a method which does NOT act on a particular instance of a Type by default (to act on a type instance you would have to pass it the handle, just like any other function which was designed to deal with a type instance).. so you might have a function in the class 'person' called Count, which would go through and count each 'person' Type. This would be a good example of using a Type Function instead of a Method.

Ok so we basically know what an Object and a Type is. Now we need to know about the Interface. In fact, by describing Type Functions and Methods, we already know what makes up an Interface! You see, the goal of object oriented design is to allow objects to interact with each other by 'sending messages' (calling type functions/methods), as mentioned above. This is done by defining an Interface for an object. Therefore you can think of an Object's Interface as the collection of Type functions (or methods) which "do" things with, to, or for, the object in question. For example the Interface for a Television might consist of: PowerButton(), ChangeChannel(). You don't care about how the TV's internal circuits operate to change the channel, you just know that by operating the interface (your remote control), and pushing a button, the channel changes! This leads into another 'buzzword' of OO design..

Encapsulation

Encapsulation is the act of stuffing all the relevant details (properties, or Fields) of an object inside the object's data structure, so that whoever is working with the object, only needs to worry about the interface. So, borrowing from our last example, say we have a Type describing a Television, laid out like this:
Type Television			'Simple type to describe a television
	Field Channel, On		'Some data for our objects
	Method PowerButton ()
		If IsOn ()
			Print "TV Turned off!";On = False
		Else
			Print "TV Turned on!";On = True
		End If
	End Method
	Method ChangeChannel(newChannel)	'Code to set the channel
		If IsOn () Then Channel = newChannel
	End Method
	Method GetChannel()	'Code to see what channel we're on
		Print Channel
	End Method
	Method IsOn()		'And code to check if the TV is on
		Return On
	End Method
End Type

'Now create an instance of a Television
myTV:Television = New Television
myTV.PowerButton				'Lets turn that baby on!
myTV.ChangeChannel (5)			'Ah maybe there's some news we can catch..
myTV.GetChannel				'And check to make sure the channel is right..
Now, notice how there are properties to describe this television, as well as methods which act upon the television. The encapsulation idea I was talking about becomes apparent when you notice that you do not have to ever access the individual fields within the type directly. This is indeed the point of encapsulation. It might seem silly in such a simplistic example to have methods which only immediately return a field, but consider this.. Now the implementation details of finding out if the TV is 'on' is hidden away under the method "IsOn()". This means that should we ever (as designers and maintainers of this Type) want to change the implementation (say change the variable name of On to tvOn), we are free to modify the Type directly and we do not have to worry about it breaking any code which has used this Type. And that's really the beauty of encapsulation. Some languages take this further.. for example in c++ you can define which class methods are Public and which are Private, which will do the job of defining a Class' interface quite nicely (by not allowing any code outside of the class itself to call that method). This is sadly not possible in BlitzMax, and so we have to make do with every type function/method being available to anywhere else in code. (This indeed sort of undermines the purpose of Encapsulation..) The advantage of packaging all the code relevant to an object within the object itself still remains, however, and is worthwhile indeed. Ok, the next two points sort of build off each other..

Inheritance and Polymorphism.

First we'll look at Inheritance. Basically, looking at our OO model, Types are there to describe real-world (or game-world) objects.. be they ships, tables, animals or whatever. However in the real world, there are lots of objects which don't necessary fall in the same "class" (or Type), but are definately very similar. For example, a DVD has similar properties to a Movie (it has a start, end, duration, etc), but also has it's own properties (it's a disk, perhaps single layered or double layered, etc) If you were to write code for an object for a DVD and an object for a Movie, they would most likely have alot of identical code. One of the purposes of OO is to cut down on redundant or similar code, and this is where Inheritance comes in. You can think of Inheritance as a relationship between two Classes of Objects. The keyword in BlitzMax which is used for this is 'Extends'. Consider the following code:
	'**Type definitions
Type Movie
	Field Title$, Duration
	Method Rent (MovieTitle$)
		Title$ = MovieTitle$
		Print "Movie rented: " + Title$
	End Method
	Method Play ()
		Print "Generic movie play method"
	End Method
	Method Stop ()
		Print "Generic movie stop method"
	End Method
End Type

Type DVD Extends Movie
	Field Extras%
	Method Play ()
		Print "Putting disk into DVD player, and pushing 'play'"
	End Method
	Method Stop ()
		Print "Stopping DVD"
	End Method
End Type

	'**Example code
vid:Movie = New Movie
vid.Rent ("Die hard")
vid.Play
vid.Stop

vid2:Movie = New DVD
vid2.Rent ("Die hard 3")
vid2.Play
vid2.Stop
In this example, we show off a few features of Inheritance. First off, we declare a simple type "Movie", and then Extend (or Inherit) it into the type DVD. This type of relationship is often described as a "is-a" relationship.. in other words saying "A DVD is-a Movie", makes sense in our model. What is happening when we use "Extends" is the entire contents of the 'base type' (in this case Movie) is included into the contents of the 'derived type' (or in this case, DVD). Therefore, without having to define it, an instance of a DVD type will have the same fields, methods, and functions, that the Movie type defined. But wait, some of them are defined again in the type DVD..? Doesn't that cause an error? Nope, not a bug, but a feature :) If you re-define, within your derived type, a type method or function which existed in the base type, you are in effect doing what is known as 'overriding' that function. You can even do this with fields, to override the data types that fields are (for example changing a string to an int). What this means is that the compiler will make sure that the proper code is being called, by deciding what type of object a handle is referring to. In this case, it knows vid2 is a DVD type, so it calls the new methods defined in the DVD type definition. If you do not override a method/function/field, then the default behaviour from the parent (or base) type is used. Therefore you can define some default behaviour for a particular Type function or method (which derived types inherit by default), and then if you choose to specialize later in one of the derived types, you can do that too! This can already reveal some advantages of inheritance, although polymorpishm will further show how useful this can be.

Further Inheritance Keywords - Abstract and Final

While we are on the subject of inheritance, it is worth noting two additional keywords, Abstract and Final. Basically you can define either an entire Type to be Abstract, or just one particular function/method within the type, although with slightly different meanings. If an entire Type is Abstract, it means that you cannot create an instance of that particular type. You can of course create instances of any types which Extend (or Inherit) that type, but not of that base type itself. An example is if you had an abstract base type Mammal, and a derived type Man. Of course you can't instantiate a Mammal (well, what is it??), since it's just an abstract reference to define further sub-classes.. but you can easily instantiate (or create an instance of) a Man.

Defining a specific method or function as abstract within a type simply means that there is no actual definition to that method/function within the base Type, but ANY Types which extend the base type MUST declare their own version of the method/function to be considered Not Abstract (ie to be able to be instantiated). Again with the Mammal example, it's like if you said there was a method called Breathe ().. if you declare it Abstract, you are essentially saying "Everything which is based off of Mammal *has* to have a way to breathe. But they all will have their own way of doing it." Also, keep in mind that by declaring any method or function abstract, by doing so you are also declaring the entire Type to be Abstract. Which of course means you can only Extend from it and not instantiate it directly.

A little quirk with Abstract, which I only just found out through a little code test:
Type Shape
	Method draw () Abstract
End Type

Type Square Extends Shape
'	Method draw ()		'uncomment these lines second to make it compile again
'	End Method
End Type

mysquare:square = New square	'uncomment this line first to see error
Basically you'll notice that I've purposely gone and extended a type which has an abstract method. I did so without defining a new method (as you are supposed to.. re-read the last paragraph if you don't understand). Turns out, if you run this code as is, you'll quickly find out (to my surprise) that "Unable to create new object of abstract type 'Type'". What? Took me a second to figure it out, but what happens is, if you Extend off of a Type which has a method declared Abstract, you will effectively make the derived type Abstract until you specifically override the Abstracted method in the parent type. Which actually does make sense. (Extending an Abstract type makes the derived type abstract as well, until you make it not so by overriding the abstract method) Uncomment the method override (the 'Method draw() in the Square Type definition) and you'll see the code compiles without errors as it should :)

As far as Final is concerned.. it also can be used to describe in either the Type definition itself, or more specifically in the Type's methods or functions. If used to describe an entire Type, it simple means that the type cannot be Inherited (by using the Extends keyword). If used to describe a Type Method/Function, it means that any derived types cannot override that particular method/function.

Polymorphism

And finally.. we come to Polymorphism. The term itself means "The ability of different object to respond to the same message in specific ways: Objects can have very different implementations of the same message. In Smalltalk polymorphic behaviour in responce to messages is independent of inheritance.", as ripped from here. Time for another code example.. this one shows off inheritance, method overriding, AND polymorphism:
'**Type declarations first
Type car
	Field Name$
	Method drive ()		'A default drive method. Only cars which do not define this would ever use it.
		Print "Moving a Car"
	End Method
End Type

Type viper Extends car
	Method drive ()		'Override the default drive method.
		Print "Driving Viper. (Wow it's fast!)"
		Print Name$
	End Method
End Type

Type neon Extends car
	Method drive ()
		Print "Driving the Neon. (Aw.. not as fast!)"
		Print Name$
	End Method
End Type	
'**Now some example code
Global anycar:car		'First we declare a handle, or pointer, to a Type. We state that it will be of type 'car'
'Then create some car objects to play with
car1:viper = New viper
car1.name = "fast car"

car2:neon = New neon
car2.name = "slow car"
'And now, the polymorphic magic begins.. set our pointer to the first car
anycar = car1
anycar.drive
'and then the second car
anycar = car2
anycar.drive
Ok so what's happening here.. First we show off how the 'viper' and 'neon' Types both Extend the 'car' type. This of course says in a nutshell "a viper is-a car" and "a neon is-a car". It also then declares a handle 'anycar' and states it is of type 'car', which is later used to show exactly what polymorphism is. It then creates two objects, one of type 'viper', and another of type 'neon'. Remember these are technically two seperate types, however since they both extend 'car', they can be thought of as being the same class as a Car. They are not the same class as each other (a viper is *not* a neon), so therefore trying to do something like car1 = car2 will get the compiler to tell us an error (same as what we're used to from old blitz types..). But. A pointer of type Car, can easily be casted to point to anything which Extends from Car. This is the 'big deal' of polymorphism. We can use a single 'pointer' (or type handle, or whatever) to a base object and use it to interact with (or 'send messages', or call methods and functions) of any of it's derived objects. So we don't care which kind of car it is, we just tell it to drive and the type declaration handles the rest for us :) (Ooh.. encapsulation again!) With this in mind, we can only do this in going 'down' the inheritance tree.. In other words, we can use a car pointer to point to any car which Extends from 'car', but we CANNOT use a 'viper' pointer to point to a 'car' object. That wouldn't make sense (It only goes one direction, in other words)

Alright! *phew* This turned out to be a little longer than I originally thought it would be! I can only hope this has helped someone out there in BlitzMax land better understand what's going on with this awesome new tool we have available to us :) It has definately been a learning experience for me, and I know it will only get better from here..! If there are any questions which come to mind or any things which I have left unclear, go ahead and post and I will do my best to answer them.

Later :)

*The first chapter in particular of Thinking in C++ can be very helpful in explaining Object Oriented Design. It is available for free, online, [a http://ortdotlove.net/~orbitz/Thinking_In_C++/Volume1/Chapter01.html]here[/a]. You may find it valuable to give at least that chapter a read, even though it's geared towards c++.. I know it definately helped me :)


SurreaL(Posted 2005) [#2]
wow. it didn't look that long in notepad I swear! Sorry if I scared anyone away :P


Tom(Posted 2005) [#3]
Good work SurreaL!


ImaginaryHuman(Posted 2005) [#4]
Hey that's a really nice tutorial, thanks! I thought I sort of understood this stuff but I learned some new things here. I didn't get lost once trying to understand any lingo or anything, so good job!


Emmett(Posted 2005) [#5]
This is a great tutorial that has really brought me closer to understanding the basic principles of what's going on.
I hope you can find some time to produce more of these.

I could not get code example #2 to work without removing the colon before the word On in the lines:
Print "TV is on!":On = True
Print "TV is off!":On = False
Worked perfect after removing the colons.

I got lost once in awhile but after running the code and re-reading the text a few times - most of it is very clear.

There is one area which is still not clear though. When you talk about Abstract you say that if:
Type Shape Abstract
Method draw ()
End Type
This entire class/type is Abstract so there can be no instance of it. So if you cannot have an instance of that type, what can you do with it?


SurreaL(Posted 2005) [#6]
Thanks for the positive comments guys :)

Emmett, oops..! The example you mention originally ran, however after looking at the source I figured I would make it slightly shorter by putting two lines onto one.. By a small oversight I used a colon (:) instead of a semi-colon (;).. I have since fixed the example in the article! (Thanks for pointing it out!) Guess it's obvious I'm still getting used to the language myself :X As for your question..

There is one area which is still not clear though. When you talk about Abstract you say that if:
Type Shape Abstract
Method draw ()
End Type
This entire class/type is Abstract so there can be no instance of it. So if you cannot have an instance of that type, what can you do with it?

Indeed there can be no direct instance of the type Shape. What you CAN do with Shape however, is Extend(/inherit) it (as in the example with the type Square). By doing so, you WILL be able to create an instance of the derived type. Basically it just means that an Abstract Type is essentially a 'concept' Type (or maybe it helps to think of it as sort of a template) which cannot serve a purpose on it's own, but can be used to derive further Types (which themselves CAN be instantiated).

I'll try to explain with another example. For our Abstract Type this time, let's say you decided to make a base Type to help describe weapons in your game, and called it Weapon. You can then define the base properties that you decide each weapon should possess (ammo, for example). Then you create specific Types to Extend Weapon (perhaps one for a laser gun, and another for a rocket launcher) These types would define their own behaviour for things that they did, while retaining the similar characteristics that Weapon defined. (ie they would probably all have their own method Shoot, and some of them might have Reload, while others might not) Anyhow, it makes sense to say that we can create an instance of a rocket launcher, because the rocket launcher has all of the necessary code to behave as it should in the world. (It knows how to shoot, how to reload, how to do whatever else you define) It does *not* make sense to create an instance of Weapon however, because Weapon by itself doesn't have enough code to do everything that is required of it. It is only Abstract.. not actually real. An Abstract type is indeed fairly useless and meaningless until it is Extended to a different Type.

In other words.. You can think of Abstract types as building blocks, or even the foundation, from which other more concrete Types can be built. It is these derived types which can be created, and used in your program :) That clear things up a bit?


ImaginaryHuman(Posted 2005) [#7]
Hey, are you making a version of BlitzPlay for BlitzMax?


Jeroen(Posted 2005) [#8]
I said it before, but DarkBasic users can be jealous of this community...lots of contributions and tutorials!


JAW(Posted 2005) [#9]
Nice tutorial SurreaL. I was planning on writing one very similar, doh. Guess I'll have to think of a different or more specific topic to write about so I can contribute something.


Emmett(Posted 2005) [#10]
Well JAW how about a detailed explanation of program flow for the sample code "starfieldpong.bmx"?


EOF(Posted 2005) [#11]
Lots to soak up there. Great contribution.

I personally think it's good practice to use () only when something gets returned. So, instead of:
myTV.ChangeChannel (5)
vid2.Rent ("Die hard 3")
If myTV.IsOn() GetLotsOfBeer=True

you can do:
myTV.ChangeChannel 5
vid2.Rent "Die hard 3"
If myTV.IsOn() GetLotsOfBeer=True



PowerPC603(Posted 2005) [#12]
A further question about Abstract:

In your example:
Type Shape
	Method draw () Abstract
End Type

Type Square Extends Shape
'	Method draw ()		'uncomment these lines second to make it compile again
'	End Method
End Type

mysquare:square = New square	'uncomment this line first to see error


Is there a specific reason to define the Abstract Draw method in the base-type?
If you would leave the Shape-type completely empty:
Type Shape
End Type


And if all other derived types all have their Draw method, then it works the same way (I think).
So you don't have to create Abstract methods in the base-type.

The only reason I can think of why a programmer puts Abstract methods in the base-type, is to see if all derived types have their own Draw method, or it would create an error, because one derived type doesn't have it Draw method.

Or am I completely wrong about this?


Bot Builder(Posted 2005) [#13]
Yes, the point of abstract types/elements is to ensure they are implemented in all derivative classes. Actually, there is a point. The point is that now all squares, along with circles, pentagons, etc, can be refurred to as a "shape", and the method "draw" used on any shape, regardless of what it is. The proper method is then called.


Slider(Posted 2005) [#14]
Is full encapsulation coming soon? I know people new to OOP will think creating get/set methods is a pain, but it's worth having full encapsulation!!!


daqx(Posted 2005) [#15]
Thanks for this tutorial, it helped me a lot ;)


H&K(Posted 2006) [#16]
Another Slant on Abstract

And that is the mantra "Think of the Object"

When Object programming we need to think of methods as "Things that we can do with ANY instance of that class (Object)"

So in the TV example we KNOW we CAN change Channel on any TV, and as such a method should exist in the base class. In addition we KNOW HOW TO Change channel on any TV ;-). And so the Method Change Channel is "Real"

On the other hand, we KNOW we CAN draw ANY shape, so again the base Type needs a method Draw(), BUT.... we don’t know HOW to draw "Shape". We Need an 'Extended' description of the shape to do that.

So the method Draw() which we know should go into shape, cannot be "real" its Imaginary or.... Abstract

So when we extend the definition of shape, (Square extends shape) we can then describe how to draw it.

We don’t need to do this at all, and can just implement Draw in each specific shape defined, But its part of the programming model that is OOP


Chris C(Posted 2006) [#17]
@JB I love your framework assistant but I think you are plain wront about calling functions, but then I'm such a chicken I like to use superstrict all the time... :D