V4 - OOP

Share your advanced PureBasic knowledge/code with the community.
User avatar
fsw
Addict
Addict
Posts: 1603
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Post by fsw »

Kale wrote:
Fred wrote:Using interfaces allows to skip the repetition of the first argument, which is a must, as it looks much better IMHO (and is faster).
Aye but surely implementing Virtual Tables can be done more elegantly. I hate the way you have to create vtables using interfaces. And using interfaces, the properties are seperate from methods. leading to an interface declaration and properties structure plus i can't find a way to set property values without an interface method. :?
You are right Properties and Methods are separated, but actually it's not a big deal.

Also the Properties are secure. Nobody can change them except he uses the Method you defined.
This way if you provide a object class you are in control how the properties are processed, which in turn is much more safe.


Virtual tables are not bad either.
Look at it that way: what you have done here

Code: Select all

Skulls\EchoValues = @EchoValues()
Skulls\MoveSkull = @MoveSkull()
Skulls\InitialiseSkull = @InitialiseSkull()
Skulls\DisplaySkull = @DisplaySkull() 
is actually YOUR sort of virtual table :wink:

This is the same with a Virtual table:

Code: Select all

DataSection
  SkullClass_Methods:
  Data.l @EchoValues(), @MoveSkull(), @InitialiseSkull(), @DisplaySkull()
EndDataSection
As you can see it's even less typing :D

BTW: here you can see what I've already done with V4:

Code: Select all

;oop style
Procedure MyProc()
  MessageRequester("Hi!", "It's me")
EndProcedure

Procedure My2ndProc()
  MessageRequester("Hi!", "No it's me")
EndProcedure

; --------------------------------------------------------------
;start main code
TheApp.ApplicationClass = Application()

;Create Main Window with default values
MainWindow.WindowClass = Window("myTitle")

;this Button will be placed with default values
MyButton1.GadgetClass = Button("MyButton")
MyButton1\Connect(@MyProc())

MyButton2.GadgetClass = Button("MyButton", 50, 150, 80, 50)
MyButton2\Connect(@My2ndProc())

;this keeps the app running
TheApp\Run("Quit?")

Debug "END: now you can clean stuff up if you need to..."

End
All checkings (IF OpenWindow bla bla bla, If CreateGadgetList bla bla bla, If ButtonGadget etc.) are done inside the classes.
Even event handling is done without a long REPEAT-UNTIL loop.

But there is still much work to do...
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Here is a little dog example:

Code: Select all

;;dog implementation of fsw's OOP example 

Interface canine 
   StoreAttributes(breed.s,name.s,age.l,weight.f) 
   Getbreed.s() 
   Getname.s() 
   Getage() 
   Getweight.f() 
   Destroy() 
EndInterface 

Structure canine_Properties 
   VTable.canine     ;address of the Methods vtable 
   breed.s      ;the rest are optional 
   name.s 
   age.l 
   weight.f 
EndStructure 
  
Global numdogs = 0          ;Set up an array for the instances 
Global Dim dog.canine(numdogs)  ;of the canine object 

Procedure StoreAttributes(*Self.canine_Properties,breed.s,name.s,age.l,weight.f) 
   *Self\breed  = breed 
   *Self\name   = name 
   *Self\age    = age 
   *Self\weight = weight 
EndProcedure 

Procedure.s Getbreed(*Self.canine_Properties) 
  ProcedureReturn *Self\breed 
EndProcedure 

Procedure.s Getname(*Self.canine_Properties) 
  ProcedureReturn *Self\name 
EndProcedure 

Procedure Getage(*Self.canine_Properties) 
  ProcedureReturn *Self\age 
EndProcedure 

Procedure.f Getweight(*Self.canine_Properties) 
  ProcedureReturn *Self\weight 
EndProcedure 

Global NewList dog_properties.canine_Properties() 

Procedure CreateDog()
  AddElement(dog_properties()) 
  dog_properties()\VTable = ?canine_methods 
  Read dog_properties()\name 
  Read dog_properties()\breed    
  Read dog_properties()\age    
  Read dog_properties()\weight 

  numdogs+1 
  ReDim dog.canine(numdogs) 
  dog(numdogs) = dog_properties() 
EndProcedure 

Procedure Destroy(*Self) 
  ChangeCurrentElement(dog_properties(), *Self) 
  DeleteElement(dog_properties()) 
  ResetList(dog_properties()) 
  ReDim dog.canine(CountList(dog_properties())) 
  numdogs-1 
  cc=1 
  ForEach dog_properties() 
    dog(cc)=dog_properties() 
    cc+1 
  Next  
EndProcedure 

;################################################################ 
; Test section, just playing around 
;################################################################ 

Procedure DebugDogs() 
For i = 1 To numdogs
  Debug dog(i)\getname()
  Debug dog(i)\Getbreed() 
  Debug dog(i)\Getage() 
  Debug dog(i)\Getweight() 
  Debug "" 
Next 
EndProcedure 

Restore dogs 


CreateDog() 
createdog()
createdog()

DebugDogs() 

dog(2)\destroy()
Debugdogs()

End 

DataSection 
   canine_Methods: 
      Data.l @StoreAttributes(), @Getbreed(), @Getname(), @Getage(), @Getweight(), @Destroy() 
   dogs: 
     Data.s "Barney","Shi-Tsu" 
     Data.l 11 
     Data.f 28.5 
     Data.s "Oliver","German Shepherd" 
     Data.l 5 
     Data.f 90.25 
     Data.s "Rover","Collie" 
     Data.l 3 
     Data.f 44.75 
     Data.s "Fuffles","Bichon" 
     Data.l 4 
     Data.f 18.5 
     Data.s "Fido","Retriever" 
     Data.l 9 
     Data.f 40.25 
     Data.s "Sam","Collie Mix" 
     Data.l 12 
     Data.f 26.75 
EndDataSection 
Here is a "SuperScrabble" object I'm working on:

Code: Select all

;;Scrabble Tile Object Definition

Interface tile 
   x(x.l = -1)
   y(y.l = -1)
   id(id.l = -1)
   name.s(n$ = "")
   value.l(v.l = -1)
   Destroy() 
EndInterface 

Structure tile_Properties 
   VTable.l     ;address of the Methods vtable 
   id.l
   name.s
   x.l      
   y.l 
   value.l
EndStructure 
  
Global numtiles.l = 0  ;Set up an array for the instances 
Global Dim block.tile(numtiles) 

Procedure id(*Self.tile_Properties,id.l=-1)
  If id <> -1
    *Self\id  = id
  Else
    ProcedureReturn *Self\id
  EndIf
EndProcedure 

Procedure x(*Self.tile_Properties,x.l=-1)
  If x <> -1
    *Self\x  = x
  Else
    ProcedureReturn *Self\x
  EndIf
EndProcedure 

Procedure y(*Self.tile_Properties,y.l=-1)
  If y <> -1
    *Self\y  = y
  Else
    ProcedureReturn *Self\y
  EndIf
EndProcedure 

Procedure.s name(*Self.tile_Properties,n$ = "")
  If n$ <> ""
    *Self\name = n$
  Else
    ProcedureReturn *Self\name
  EndIf
EndProcedure 

Procedure value(*Self.tile_Properties,v.l = -1)
  If v <> -1
    *Self\value  = v
  Else
    ProcedureReturn *Self\value
  EndIf
EndProcedure 

Global NewList tile_properties.tile_Properties() 

Procedure CreateTile(id.l) 
  AddElement(tile_properties()) 
  tile_properties()\VTable = ?tile_methods 
  Read a
  a$=Chr(a)
  tile_properties()\id = id
  tile_properties()\name = a$
  tile_properties()\x = 0
  tile_properties()\y = 0
  Read tile_properties()\value
  numtiles+1 
  ReDim block.tile(numtiles) 
  block(numtiles) = tile_properties() 
EndProcedure 

Procedure destroy(*Self) 
  ChangeCurrentElement(tile_properties(), *Self) 
  DeleteElement(tile_properties()) 
  ResetList(tile_properties()) 
  ReDim block.tile(CountList(tile_properties())) 
  numtiles-1 
  cc=1 
  ForEach tile_properties() 
    block(cc)=tile_properties() 
    cc+1 
  Next  
EndProcedure 

DataSection 
   tile_Methods: 
      Data.l @x(), @y(), @id(), @name(), @value(), @destroy() 
      ; BL(4) A(16) B(4) C(6) D(8) E(24) F(4) G(5) H(5) I(13) J(2) K(2) L(7)
      ; M(6) N(13) O(15) P(4) Q(2) R(13) S(10) T(15) U(7) V(3) W(4) X(2) Y(4) Z(2) 
   tiles: 
      Data.l 65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,66,3,66,3,66,3,66,3
      Data.l 67,3,67,3,67,3,67,3,67,3,67,3,68,2,68,2,68,2,68,2,68,2,68,2,68,2,68,2,69,1,69,1,69,1,69,1,69,1,69,1
      Data.l 69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,70,4,70,4
      Data.l 70,4,70,4,71,2,71,2,71,2,71,2,71,2,72,4,72,4,72,4,72,4,72,4,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1
      Data.l 73,1,73,1,73,1,73,1,73,1,74,8,74,8,75,5,75,5,76,1,76,1,76,1,76,1,76,1,76,1,76,1,77,3,77,3,77,3,77,3
      Data.l 77,3,77,3,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,79,1,79,1,79,1,79,1,79,1
      Data.l 79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,80,3,80,3,80,3,80,3,81,10,81,10,82,1,82,1,82,1,82,1
      Data.l 82,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,84,1
      Data.l 84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,85,1,85,1,85,1,85,1,85,1,85,1
      Data.l 85,1,86,4,86,4,86,4,87,4,87,4,87,4,87,4,88,8,88,8,89,4,89,4,89,4,89,4,90,10,90,10,42,0,42,0,42,0,42,0
EndDataSection 

Restore tiles 

For i=1 To 200
  CreateTile(100000+i)
Next 
And now any method can be used on any of the tile objects, for example:

Code: Select all

debug block(195)\value()
Last edited by netmaestro on Tue Feb 21, 2006 12:31 pm, edited 8 times in total.
BERESHEIT
Kale
PureBasic Expert
PureBasic Expert
Posts: 3000
Joined: Fri Apr 25, 2003 6:03 pm
Location: Lincoln, UK
Contact:

Post by Kale »

Good discussion this :) i understand why properties should remain private within classess/objects but sometimes i like to be simple (no bloat ;) ).
fsw wrote: Virtual tables are not bad either.
Look at it that way: what you have done here
Code:

Code: Select all

Skulls\EchoValues = @EchoValues() 
Skulls\MoveSkull = @MoveSkull() 
Skulls\InitialiseSkull = @InitialiseSkull() 
Skulls\DisplaySkull = @DisplaySkull() 
is actually YOUR sort of virtual table   
This is the same with a Virtual table:

Code: Select all

DataSection 
  SkullClass_Methods: 
  Data.l @EchoValues(), @MoveSkull(), @InitialiseSkull(), @DisplaySkull() 
EndDataSection
Sure but in my next project im going to be dealing with maybe 20-30 different classes i want to keep things simple, i dont want to lob all vtables into the data section. I might use includes and one per class, incuding all associated interfaces, vtables, etc. :)
netmaestro wrote:

Code: Select all

dog(2)\destroy() ;Oliver was sick anyway
LMAO! :lol:
--Kale

Image
Kale
PureBasic Expert
PureBasic Expert
Posts: 3000
Joined: Fri Apr 25, 2003 6:03 pm
Location: Lincoln, UK
Contact:

Post by Kale »

Fred wrote: In some object langage like java, it's almost forbidden to access a property without encapsulate it. It makes the things more modular and easier to maintain (for example if you need to add a check later, it's easier to put it in the function, things you can't do when you allow to affect directly a value to a structure element).
Yep, this is how i was originally doing things but while using arrays to hold the objects, i used a method to set a property but this didn't work correctly. For example in array index (0) i set a property to '1', But this set that particular property to '1' in ALL the objects inhabiting all indices within the array! I'll post some code later to demonstrate this, i'm unsure wether its a bug or me drinking too much wine! ;)
--Kale

Image
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

fsw wrote:As you can see with prototypes (as it is now) you have to use your object as 1st parameter:
which is actually oop-like but not oop.
Fred wrote:Using interfaces allows to skip the repetition of the first argument, which is a must, as it looks much better IMHO (and is faster).
netmaestro wrote:Yes, the repetition of the argument is not cool.
Indeed. That's ugly and it seems the only fault of the prototyped way.
But someone can explain a why to the Fred's affirmation? Why it is slower? in theory it is the same, because the parameter is included in the Procedures in both cases, or I miss some detail?
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

@Kale, don't forget you can put many datasections in your program anywhere at all. So your idea of creating an include file for each object works out very well. You can just put the interface, methods, properties and vtable in a separate include file for each object and then write one very small, very readable main program which does very large things.
Last edited by netmaestro on Tue Feb 21, 2006 12:47 pm, edited 3 times in total.
BERESHEIT
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

Psychophanta wrote:But someone can explain a why to the Fred's affirmation? Why it is slower? in theory it is the same, because the parameter is included in the Procedures in both cases, or I miss some detail?
If you look to the generated asm, the first parameter is cached in a register (eax) when using an interface, which means than it's not evaluated at second time (which can be problematic when using an array for example, as it gets evaluated twice, once to locate the prototype, and another for the 1st parameter). Also, duplicating is never good in a code, it can lead to hard bug to track (a small mistake can happen quickly) ;).
Kale
PureBasic Expert
PureBasic Expert
Posts: 3000
Joined: Fri Apr 25, 2003 6:03 pm
Location: Lincoln, UK
Contact:

Post by Kale »

netmaestro wrote:@Kale, don't forget you can have many datasections in your program located anywhere at all.
:shock: I didn't know that! Very Cool! :D
netmaestro wrote:So your idea of writing one pbi per class works out very well. Just put the interface, methods, property list and datasection with vtable for each class in it's own pbi and you're ready to write one very small, very readable main program that will do very large things.
Yes, i love custom interfaces for encapsulating powerful functionality, etc.. it makes the main program infinately more readable, simple to use and more powerful! :)
Fred wrote:If you look to the generated asm, the first parameter is cached in a register (eax) when using an interface, which means than it's not evaluated at second time (which can be problematic when using an array for example, as it gets evaluated twice, once to locate the prototype, and another for the 1st parameter). Also, duplicating is never good in a code, it can lead to hard bug to track (a small mistake can happen quickly) .
I will probably be using real interfaces for my project then, because i want maximum speed. :wink:
--Kale

Image
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

You guys are way too clever for your own good!

Okay, I am going to lower the standard a bit. Is doing something like:

Code: Select all

Interface canine
the same as defining a class?

Code: Select all

Global Dim dog.canine(numdogs)
This array of objects now belongs to that class?

And it seems a "class" or interface can get another "class" or interface attached, like structures in structures - so this gives inheritance?

How is polymorphism handled?

Looking at all the code here (looking, not necessarily understanding) it seems that it should be possible to interface with an OOP Lib - but how?


Dumbed down the thread - there goes the neighbourhood.
@}--`--,-- A rose by any other name ..
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Interface canine would be like defining the methods of the canine object.
Global Dim dog.canine(numdogs) sets up an array of the instances of the canine object.
Last edited by netmaestro on Tue Feb 21, 2006 12:32 pm, edited 4 times in total.
BERESHEIT
Kale
PureBasic Expert
PureBasic Expert
Posts: 3000
Joined: Fri Apr 25, 2003 6:03 pm
Location: Lincoln, UK
Contact:

Post by Kale »

Object Orientated Programming is a method of programming using objects to mimic real world objects. Everything that this object needs and whatever functionality it performs is encapsulated within its definition. These definitions are called Classes in other languages such as Java or C++. In the object definition (Class) you can define variables to hold information, AND (and heres the cool bit) you can define procedures to manipulate these variables. These procedures can also be passed variables from outside the Class too by using normal procedure parameters.

The variables that you define within Classes are sometimes called Properties and the procedures defined within are sometimes called Methods.

When you create an Object you actual create an instance of the Class (which is it's definition) and all the internal Properties and Methods are duplicated for use with that Object only! If another Object is created from the same Class then that object has a completely different set of Properties and Methods.

Once an Object is created you can then access all the Methods via the Object name. In Purebasic the properties can only be accessed via a Method.

PsuedoCode:

Code: Select all

Object.Class
Object\SetPropertyMethod(Property)
Object\GetPropertyMethod(Property)
Object\DoSomething(Parameter)
How is polymorphism handled?
It isn't in PB. :P
--Kale

Image
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

Post by NoahPhense »

wow .. this oop is getting interesting ..

- np
Straker
Enthusiast
Enthusiast
Posts: 701
Joined: Wed Apr 13, 2005 10:45 pm
Location: Idaho, USA

Post by Straker »

Kale wrote:In Purebasic the properties can only be accessed via a Method.
And many would say thats the only way properties should be accessed. That's what Setters and Getters are for.
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

Straker wrote:
Kale wrote:In Purebasic the properties can only be accessed via a Method.
And many would say thats the only way properties should be accessed. That's what Setters and Getters are for.
Java addict ? ;)
User avatar
fsw
Addict
Addict
Posts: 1603
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Post by fsw »

Straker wrote:
Kale wrote:In Purebasic the properties can only be accessed via a Method.
And many would say thats the only way properties should be accessed. That's what Setters and Getters are for.
Yep.
But since PureBasic is able to have predefined parameters for procedures (like freebasic) we can scrap SetCaption/GetCaption (as example) and do:

Code: Select all

Banana = MyWindow\Caption() ;this gets the Title
and
MyWindow\Caption("MyTitle") ;this sets the Title
This is possible because with V4 we can declare procedures this way:

Code: Select all

Procedure WindowClass_Caption(*Self.WindowClass, Title.s = "-1")
  If Title = "-1"
    ;get the Title
    ProcedureReturn *Self\Caption
  Else
    ;set the Title
    *Self\Caption = Title
  EndIF
EndProcedure
No Set/Get needed anymore.

Isn't this 8)

And if we could do:

Code: Select all

#define "->"  ""
we could

Code: Select all

MyWindow->Caption("MyTitle")
and it would look like c++ :P
Post Reply