Casting objects - Polymorphism

BlitzMax Forums/BlitzMax Beginners Area/Casting objects - Polymorphism

Volker(Posted 2008) [#1]
Hello,
let's take this code:
SuperStrict

Type TAlien
	Field health:Int = 1
	'
	Method PrintHealth()
	
	End Method
End Type

Type TAlienSmall Extends TALien
	Field health:Int = 2
	'
	Method PrintHealth()
		Print "Method PH in TAlienSmall"
	End Method

End Type

Type TAlienBig Extends TAlien
	Field health:Int = 3
	Method PrintHealth()
		Print "Method PH in TAlienBig"
	End Method
End Type

' Main
Local alienlist:TList = New TList
Local as:TalienSmall = New TAlienSmall
Local ab:TAlienBig = New TAlienBig

alienlist.AddLast(as)
alienlist.AddLast(ab)

For Local alien:TAlien = EachIn alienlist
	Local castedalien:TAlien = TAlien(alien) ' casting type of object
	'
	castedalien.PrintHealth
	Print castedalien.health
Next

With the line "Local castedalien:TAlien = TAlien(alien) "
I can call the right methods of the objects, but how
do I get access to the fields of the objects that are not Baseobjects?
I can use a method like "ReceiveHealth:Int()" which will
return the correct value(2,3) but is there a smarter way?


tonyg(Posted 2008) [#2]
Don't have a 'Field Health' in your baseclass.
This might help.



Volker(Posted 2008) [#3]
Thanks. This works too with 'Field Health' in the baseclass defined.
But thats what I want to avoid; defining a method for every
field I want to access.
If the object has a lot of fields I can define methods
like ReceiveHealth(), ReceiveDamage() and they will return
the right value. But that can be a lot of methods.


Kurator(Posted 2008) [#4]
Using those Methods (usually called Setter and Getter) are used in many OO-Languages.

It gives you the opporunity to Check any of the values you want to set.
It gives you the opporunity to return values independent of your base implementation of the class.

One of the Basic priniples in OO ist to keep the inner values a secret, and let in only change by the methods you want it to.

unfortunately this is not really implemented in bmax, c# and java use modiefiers so you can simply change the permission of every single class member




Volker(Posted 2008) [#5]
Using those Methods (usually called Setter and Getter) are used in many OO-Languages.

Now I know why every object in WxMax has such a bunch of methods..
One of the Basic priniples in OO ist to keep the inner values a secret, and let in only change by the methods you want it to.

Thanks for the explanation, I will do it this way to keep
my code encapsulated.


dmaz(Posted 2008) [#6]
I disagree... a little. the point of encapsulation is to hide parts of the interface that are likely to change or that other shouldn't be touching. that does [edit]not[/edit]translate into "don't directly access variables" if we had Private and Public options for variables this wouldn't be an issue. now on the other hand... always using setters and getters does give you many advantages and keeps your interface consistent but since BMax doesn't have Properties it can be quite cumbersome.

The author of the type or class should be free to implement a module in any way which fulfills it's requirements. Functions do add overhead and as mostly game programmers here you might need the tightest loops. there is no problem with directly accessing the field as long as you stay consistent in your design.

to answer you first question...
I can call the right methods of the objects, but how
do I get access to the fields of the objects that are not Baseobjects?


only by casting them to the correct type. which you did not do in your example. your for loop casted the returned objects from alienlist to TAlien. your next line did the exact same thing for castedalien.

you need to know what and why you are casting... to access those fields you need to directly cast to TAlienSmall or TAlienBig. but that's not what you want here....

my first question to you would be; why did you think you need a health field in each type? doing that means you are overloading the field that's already in the base. you can do this BUT there is generally no reason to and it is most often a product of bad design and should be avoided.

all your aliens have health right? that means it should be in your base ONLY.

here is what I would do. (without reflection)

SuperStrict

Type TAlien
	Field health:Int = 1
	Field name:String = "Alien"
	'
	Method PrintHealth() 
		Print "Health in "+name+" = "+health
	End Method
End Type

Type TAlienSmall Extends TALien
	Method New()
		health = 2
		name = "AlienSmall"
	End Method
End Type

Type TAlienBig Extends TAlien
	Method New()
		health = 3
		name = "AlienBig"
	End Method
End Type

' Main
Local alienlist:TList = New TList
Local as:TalienSmall = New TAlienSmall
Local ab:TAlienBig = New TAlienBig

alienlist.AddLast(as)
alienlist.AddLast(ab)

For Local alien:TAlien = EachIn alienlist
	'
	alien.PrintHealth
	Print alien.health
Next


and as you continue to code you will probably find it's easier and more convenient to use Create(blah:blah,blah:blah) *functions* in addition to New() *methods*. Also, encapsulating your lists, especially your base list (but I say all of them) will gain you many advantages.

I can show you that for this example if you would like.


Kurator(Posted 2008) [#7]
Correct dmaz :)

Sometimes I am too blind.

Of course health should only be in the base type. Sorry for the misinformation


Volker(Posted 2008) [#8]
all your aliens have health right? that means it should be in your base ONLY.

My TAlien's baseclass was a class TUnit. And a TUnit has no health.
So I defined health in every class and used the creator new().
That caused trouble. The way I did it:

With health defined only in TAlien my code works fine.

... to access those fields you need to directly cast to TAlienSmall or TAlienBig. but that's not what you want here....

Can I have an example? I'm sure I will need it in the future.

Also, encapsulating your lists, especially your base list (but I say all of them) will gain you many advantages.
I can show you that for this example if you would like.

Would be appreciated.


Czar Flavius(Posted 2008) [#9]
Why do you need to extend all these different kinds of alien? What's wrong with one alien type, with a health field (and strength/image/etc fields) but with their own particular values. Then just have a create alien function with the stats of the desired alien as parameters.

You don't need a new type for every type of unit in the game! Just one type can represent them all.


Kurator(Posted 2008) [#10]
These are two valid casts based on your class hierachy:
Local BigAlien:TAlienBig = TAlienBig(aVariableofTUnitType)
Local BigAlien:TAlienBig = TAlienBig(aVariableofTAlienType)



Volker(Posted 2008) [#11]
Why do you need to extend all these different kinds of alien?

Because they all can do very different things. Follow a path or a complex set of orders, trace the playership, start like a rocket in 'scramble', fly sinuscurves..
This would be a very big update method if I only use one alien.
I could combine some of them, but I use this project to grok OOP.


H&K(Posted 2008) [#12]
You are still putting health in the wrong place. (well, not wrong, but ill advised place)

IF Every derrived object is going to need access to a specific field name. ie Health, then it shuold be in the base class.

With health defined only in TAlien my code works fine
We know

My TAlien's baseclass was a class TUnit. And a TUnit has no health
Well. Are you sure? If you are sure that SOME units will not have health then fair enough put health in Talien

However, TAlien Small has TWO health fields
ATalienSmall.Health
ATailienSmall.Super.Health

And its the ATalienSmall.health set to 99

SO it should ONLY be in Talien

Edit. I should have read the whole thing before I posted. Oh well


Volker(Posted 2008) [#13]
Edit. I should have read the whole thing before I posted. Oh well

My posting was a bit misleading.

Well. Are you sure? If you are sure that SOME units will not have health then fair enough put health in Talien


TUnit is the baseclass for ALL graphical objects. I read it here in the
forums to derive all of them from one baseclass.
And not all all of them have health.


Czar Flavius(Posted 2008) [#14]
Here is something interesting you could do, have whole bunch of seperate movement types containing the methods for that type of movement, all extended from a base movement type.

Then each game item has a field that momvent type. Then you just do alien.movement.update()

It sounds complicated but might simplify things. You can just use one alien type but, esentially, plug-n-play with completely different movement and even extend this to attack methods as well.


Volker(Posted 2008) [#15]
I have something similar to this.
A Type TOrderUnit in which two lists of meshcommands (TOrders)like
move, translate, rotate, scale, delay etc. are executed over time.
Two of them so I can rotate and move the same time.
I have kept it meshspecific, so it can be used in later projects.
There is even a simple Editor to create this lists and test them :-)

@dmaz:
What about this?:
Also, encapsulating your lists, especially your base list (but I say all of them) will gain you many advantages.
I can show you that for this example if you would like.



dmaz(Posted 2008) [#16]
just put the list as a global in the type...

but you also want to add create functions and remove methods. you want remove methods for sure because we -very- much want to store the TLink so we can quickly remove the item from the list when dead. here I'll add the remove methods...

in the example above I only put a remove method in the base type but you should get in the habit of having a remove method in each and every type for any cleanup that -might- need to be done.

in the extend types the remove method should clean up anything that it needs to and then call "super.Remove" to run the base remove method.

[EDIT] I added all the remove methods just to be clear... you don't need them in this example but it's just a better habit to get into.


Volker(Posted 2008) [#17]
The global list in TAlien is only created once when
the first time an TAlien object or an instance of it is created. Right?
If I would use just an "list:tlist=new tlist"
a new list is created each time a new TAlien is build!?

Another point:
I have no clue why the constructor New() in TAlien, which adds the object to the list, is executed by creating: "New TAliensmall".
Is the Super Type Object Creator called additional to the one in
the TAliensmall instance ?


Brucey(Posted 2008) [#18]
Is the Super Type Object Creator called additional to the one in
the TAliensmall instance ?

All New() methods are called automatically in the hierarchy. Just like constructors in Java.


dmaz(Posted 2008) [#19]
"global"s in a type are not associated with an instance but with the type itself. think of them being initialized at program start. just like functions you can access before any instances are created. TAlien.list.ClearList is valid and so would be TAlienBig.list.ClearList.

like Brucey said, New() is special and all of them will be called in order with out using super.

If I would use just an "list:tlist=new tlist"
a new list is created each time a new TAlien is build!?
I'm not sure what you are asking here?


Volker(Posted 2008) [#20]
just like functions you can access before any instances are created.

Oh yes, I see:
Type TAlien
	Global list:TList = New TList
End Type
Print TAlien.list.Count()
works without creating an object.

I'm not sure what you are asking here?

Doesn't matter. The question is already answered in your first sentence.


Volker(Posted 2008) [#21]
just put the list as a global in the type...

@dmaz
Just finished spending half a day tracking down a memory leak.
Just for one small little object I had no internal list and remove method;
instead I got a cyclic reference.
The way you showed to encapsulate the lists really works perfect and
is a great help for cleanup.
Thank you!


dmaz(Posted 2008) [#22]
you're welcome... I'm happy to help.