Why OOP?

New to FreeBASIC? Post your questions here.
notthecheatr
Posts: 1759
Joined: May 23, 2007 21:52
Location: Cut Bank, MT
Contact:

Why OOP?

Post by notthecheatr »

originally posted at http://games.freebasic.net/forum/ but someone thought it would be a good tutorial for a beginner so I'm posting it here too. Enjoy.

OOP might be a really nice convention for programmers to adopt when writing their big projects that they're being paid to write, but why should a game programmer bother with it? This is essentially the question asked by someone recently rather off-topically in a discussion. Rather than start a heated debate on the pros and cons of OOP (though there were plenty of replies and it started getting there...) I'm going to try to answer that question here.

We start with the origin of procedural programming. In the beginning, you didn't have procedures. You basically had spaghetti code - along with capital letters, line numbers, and all that ugly stuff:

Code: Select all

100 PRINT "I AM A DOOF."
200 GOTO 100
Although it doesn't matter much in a small program like that, it gets to be extremely difficult in large programs. Hard to read, etc. Even hard to write.

So programmers somewhere, one day, decided to do this:

Code: Select all

Do
  Print "I am a Doof."
Loop
and furthermore, they even decided to do this:

Code: Select all

Sub printDoof
  Print "I am a Doof."
End Sub

Do
  printDoof
Loop
How very structured of them! Now for some, it may have been easiest to stick with the GOTO's. Amazing though it may seem, there are still some programmers who use it. I don't know how they can write anything useful (and really try to debug, or make any modifications to it) that way, but some do. But for the most part, programmers today have adopted much more structured syntax. Now it's no more powerful - you can still write the exact same code with GOTOs as you can procedurally - but it's much easier for we humans to understand, debug, modify, maintenance, etc. So we use it.

With structured programming, someone eventually figured out a rather neat idea. You see, your Subs and Functions might tend to get kind of long:

Code: Select all

Sub moveSprite (sprite As FB.Image Ptr, x As Integer, y As Integer, vx As Integer, vy As Integer, suchandsuch As uInteger, thisthatandtheother As Byte)
  '...
End Sub
See what I mean? You will be passing twenty different parameters to it, and eventually you realize it would be a lot easier if you could group these parameters together. So here comes the concept of the UDT. Put a bunch of variables in a single structure, like a package, then send the whole thing to the Sub, which means you only have to pass one (or maybe a couple) parameters, instead of twenty.

Code: Select all

Type myUDT
  As Integer a, b, c, d, e, f, g, h, i, j
End Type

'Which do you like better?
Sub mySub1 (param1 As myUDT)
End Sub

Sub mySub2 (a As Integer, b As Integer, c As Integer, d As Integer ...)
End Sub
But there's another factor we need to look at. That is scope. In the beginning, everything was global. That means
you would create a variable for your program

Code: Select all

110 DIM A AS STRING = "I AM A DOOF."
120 PRINT A
130 GOTO 120
and the variable would be accessible everywhere. Of course, that was because you didn't even have procedures yet, so it was practically pointless to hide anything from any other part of the program. But once you had procedural programming, it became useful to make variables inside procedures that weren't necessarily accessible (in fact, didn't necessarily even exist) anywhere else. Then you could have a variable called X in your main program and a variable called X in your procedure and they would be entirely different variables, and neither could access the other. If the main program needed to give some information to the procedure that the procedure didn't have, it could simply pass a parameter to the procedure. But this didn't always work, so we still had some global variables lying around:

Code: Select all

Dim Shared As Integer mynumber

Sub mySub
  Print mynumber
End Sub

mynumber = 3
mySub
mynumber = 5
mySub
Now the same variable name is the same variable whether inside the procedure or out. And many people thought this was great, because then you don't have to have a lot of parameters passed to and from your procedures (these people didn't know about UDTs yet, of course).

But there's just a little problem. What if you have a global variable and you have twenty different procedures that access it, maybe even modify it, and who knows what happens then? Maybe a bank program storing the balance of an account as a global variable. A bug is bound to happen sometime somewhere, with all that code, and you suddenly realize that the balance is off - way off. So which one of those twenty different procedures messed it up? And remember, this is a bank program, so it's probably made up of hundreds of lines of code, each of which might potentially access the account balance. That means hours of debugging for you. And in case you think I'm only talking about business programs - what about games? What if you store the sprite position x and y in global variables, you have twenty different procedures moving the sprite around and drawing it on the screen, and you suddenly notice the sprite is going all over the place on the screen and it's not where it's supposed to be? Then what? So which of those procedures did it?

Scope solves this. Only certain procedures can modify certain things, so you know which procedures to check and see if they modified it - and most of them don't, because you only let certain ones do it. So what if those procedures need to modify it? Then you pass things ByRef, and at least now you know which one did it. But that's not always safe, so we could run into problems there. Enter the UDT. It solves the problem of too many parameters, and since you pass it as a parameter to the procedures, you don't have the problems of global variables. Everything is in its proper scope, so you have one less thing to worry about. Unfortunately, if the procedures want to modify the variables inside the UDT, you have to pass it ByRef. And then you can have problems that way just like with global variables, because if you pass it ByRef to everything, you'll still have twenty procedures potentially modifying variables and messing things up. Now what? We need more scope rules!

This is what encapsulation is all about. You put the variables inside a UDT, but make some of them Private. What's that mean? That means nobody else can access those variables! They're hidden inside the UDT, so even if I pass the UDT to a procedure, the procedure can't access those variables. In fact, even the procedure (or main program) that creates the UDT can't access the variables:

Code: Select all

Type myUDT
  Public:
    As Integer a
  Private:
    As Integer b
End Type

Dim something As myUDT

'This is OK
something.a = 3
Print something.a

'This is NOT OK
something.b = 5
Print something.b
But of course that begs the question who CAN access the variables? Because if nobody can access the variable at all, then what's the point? In a sprite, you might hide the x and y variables so nobody can just move the sprite around whenever they want. But if nobody can access the (x, y) variables, then how does ANYONE move the sprite? And how would we know where to draw it?

This is why we add things called methods to the UDT. It's the beginning of OOP. It may seem a little strange, but it really solves a lot of problems. Now only ONE procedure can modify the sprite's position, and if there's any bugs you know who did it!

Code: Select all

Type mySprite
  Public:
    Declare Sub move (newx As Integer, newy As Integer)
    Declare Sub draw ()
  Private:
    As Integer x, y
End Type

Sub mySprite.move (newx As Integer, newy As Integer
  x = newx
  y = newy
End Sub

Sub mySprite.draw ()
  PSet (x, y), &hffffff
End Sub
Now you could just as easily do it like:

Code: Select all

Type mySprite
    As Integer x, y
End Type

Sub moveSprite (tsprite As mySprite, newx As Integer, newy As Integer
  tsprite.x = newx
  tsprite.y = newy
End Sub

Sub drawSprite (tsprite As mySprite)
  PSet (tsprite.x, tsprite.y), &hffffff
End Sub
But then you lose the encapsulation and you still have scope issues because anyone else can access x and y. This example also illustrates something else we need to examine: style.

You might not think it matters, but it does. For example, although indentation doesn't really have any effect on what a program does, it's still a lot easier to read

Code: Select all

If something = 1 Then
  Do
    Print "Hi"
  Loop While something = 1
End If
than

Code: Select all

If something = 1 Then
Do
Print "Hi"
Loop While something = 1
End If
And there are other areas this applies. Now if you have a UDT and a bunch of procedures that deal with that UDT specifically, it eventually becomes logical that those subs should be part of that UDT, rather than separate entities. It also simulates real life; for example, in the corporate structure, your executive gives out tasks for his employees to do. The boss doesn't balance finances using the employee, he tells his employee to do it! So likewise it seems much more logical to tell the sprite to draw itself, rather than to tell a procedure to draw a sprite using the UDT that contains it. Or a map object, for example:

Code: Select all

Type mapType
  As Integer x, y
  As Any Ptr tiles
End Type

Declare Sub loadMap (filename As String, map As mapType)
Declare Sub drawMap (map As mapType)
Declare Sub moveMap (map As mapType, x As Integer, y As Integer)
Notice anything about all these subs? They all have a parameter called "map As mapType"! But if you use OOP, you don't have that. In fact, you can just call the subs "map.load, map.move, and map.draw" instead of "loadMap, drawMap, and moveMap". Nor do you pass a map parameter to them; they're part of a map object, so they have a hidden parameter passed to them.

Hopefully you're getting an idea of why OOP makes sense. It's really just another abstraction which takes some ideas to the next level. UDTs don't exist in real life; neither do procedures for that matter. At the low level, the variables are separate, the procedures in the UDTs are not "part of" anything at all, and all procedures are really just Goto statements (actually, it's a little better than that but I won't go into the details). But OOP saves you from scoping problems, allows you to work much more easily, and makes your code look nicer and more logical too!

But OOP goes beyond making code nicer and more logical. It makes a lot of things simpler, and generally lets you do some very interesting things you otherwise couldn't do. For example, strings. Normally the + is used to add two numbers, but what if you want to use it to concatenate two strings? That's what operator overloading is for! If you make a string object, you can use the + operator for the object and it does something totally different! You can do this with pretty much all the other standard operators too.

And then there's RAII.

cha0s wrote a tutorial about RAII too, but some people might not understand it so well. What it basically means is, when you create an object the object keeps track of all the resources it needs. Take for example a buffer in memory. If you create a buffer (say, to store your map data) then you need to use a pointer, and you need to Allocate/ReAllocate/DeAllocate. Now what if you forget to DeAllocate? You may eventually get memory leaks and problems! But if you put it inside an object, the object will automatically allocate the memory it needs when it needs it, and as soon as the object is destroyed (which will be at the end of the program or at the end of the procedure it's created in, depending on the scope) it will automatically deallocate the memory, thus ensuring you won't get any leaks (or wasted memory) by forgetting to deallocate a buffer when you're done with it.

This is done with Constructors and Destructors, which are two special kinds of procedures that are called automatically as soon as the object is created or destroyed. The other nice thing about them is that they let you set the object up, make sure the object is valid before you call any other methods. For example, you might want to draw your map, but what if you forgot to load it? Then the pointer to the buffer containing the map data is invalid and when you try to draw the map your program will likely crash. But if you have a Constructor, which gets called automatically no matter what, then it can set a special flag inside the object explaining that the map has not been loaded so it shouldn't be used. You can also allocate the pointer, so when the map loading routine needs to reallocate there's something to reallocate (and of course the destructor will deallocate it):

Code: Select all

Type mapType
  Public:
    Declare Constructor ()
    Declare Destructor ()

    Declare Sub load (filename As String)
    Declare Sub draw ()
    Declare Sub move (newx As Integer, newy As Integer)
  Private:
    As uByte Ptr _map_data
    As uInteger _map_width, map_height
    As Integer _x, _y
End Type
Now if we were to call draw() without a constructor, it would check the buffer at _map_data for tiles to draw. But the map hasn't been loaded yet, so the buffer is empty! This could very well cause a crash! But if the constructor is used, the constructor sets everything up ahead of time so no errors can occur.

Now of course in practice, you would be very careful not to call draw() until you call load(). But if you forget, or make a mistake, setting things up this way can save you a lot of trouble.

Another thing OOP lets you do is Properties. Now as I said earlier we want to hide all the variables inside the object where nobody else can access them, because otherwise we can have problems. But always using a sub to access the variables may seem rather uncomforable. This is why we use properties. They seem like variables, and they act just like variables - for example, if x is a property of myUDT, then you can do

Code: Select all

myUDT.x = 3
Print myUDT.x
but in reality, properties are actually a special kind of procedure, meaning when you try to modify a variable you aren't just directly modifying it, you're actually instructing the object to modify it. This way, for example if you had a property map.x the map could redraw itself automatically when you modify map.x. This wouldn't happen if you tried to modify the variable directly, but since a property is really a sub in disguise you can do that. This way, you avoid the scope problems but you don't have to use the rather uncomfortable sub just to modify things.

Of course, how you do things is entirely up to you. It might be easier at first not to use properties, operators, or even constructors and destructors. These things are just special extras that come with the OOP paradigm. You may want to start out just using methods and keeping all variables Private. It's still much safer and smarter than using UDTs with everything public. And you'll start to get a feel for OOP, and maybe even learn to enjoy it and prefer it to just plain procedural programming.

OOP is not the be-all and end-all solution to everything. I don't use OOP everywhere, by any means - but I do use it whenever I can, because it can save me a lot of trouble and make sure I do things right. And by the way, OOP and procedural programming can be done side-by-side. As I said, I don't use OOP for everything - for some things, a plain old-fashioned function or sub will work just fine. But when it's possible, when it's useful, when it makes things easier, safer, and cleaner, I use OOP.
DaveUnit
Posts: 239
Joined: Apr 20, 2006 15:47
Location: Central MA

Post by DaveUnit »

Very well written! This will help people understand the usefulness of OOP more.
PaulSquires
Posts: 1002
Joined: Jul 14, 2005 23:41

Post by PaulSquires »

Very impressive explanation. Thanks for sharing this!
Mico
Posts: 167
Joined: Oct 14, 2005 6:09
Location: Italy

Post by Mico »

Well done, notthecheatr! A really outstanding explanation.
Enq Movain
Posts: 51
Joined: May 31, 2005 13:24
Location: Canada

Post by Enq Movain »

I like this post, this actually helped me understand properties, constructors and destructors better then the FB documentation did. :D

Enq
anonymous1337
Posts: 5494
Joined: Sep 12, 2005 20:06
Location: California

Post by anonymous1337 »

Yes. This post is wonderful. *clap clap clap*

Ye be warned about properties and operator overloading, though! Using them a lot doesn't make your code any better ;-) Use'em when you need'em. Honestly, I seldom have reason to like them much.
Mentat
Posts: 332
Joined: Oct 27, 2007 15:23
Location: NC, US
Contact:

Post by Mentat »

Neat. I didn't know methods could be inside UDTs.
RayBritton
Posts: 306
Joined: Jun 02, 2005 7:11
Contact:

Post by RayBritton »

Thank you, that explained OOP really well.
relsoft
Posts: 1767
Joined: May 27, 2005 10:34
Location: Philippines
Contact:

Post by relsoft »

This would be a really good article for QB express. :*)
Lachie Dazdarian
Posts: 2338
Joined: May 31, 2005 9:59
Location: Croatia
Contact:

Post by Lachie Dazdarian »

Let's not go there. :P
Mentat
Posts: 332
Joined: Oct 27, 2007 15:23
Location: NC, US
Contact:

Post by Mentat »

I think it would be very good (not to mention that Pete needs material). I never really progressed into C++and Java because I didn't undestand OOP, WIN32, and functions. And I got a C++ compiler well before (okay, a few months) I came to QB.
mark bower
Posts: 395
Joined: Dec 28, 2005 6:12
Location: arcadia, CA USA

Post by mark bower »

Indeed, I very much appreciate this comprehensive post and the time it must have taken to write it. I have barely achieved the use of Type, and probably only understood about 80% of the post, but it gives me the vision of moving into OOP.
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Post by cha0s »

Hey, I got a request to sticky this, so here it is. :)
Mysoft
Posts: 836
Joined: Jul 28, 2005 13:56
Location: Brazil, Santa Catarina, Indaial (ouch!)
Contact:

Post by Mysoft »

heh, dont over do it! :}

at least now you know that you are a doof!
h4tt3n
Posts: 698
Joined: Oct 22, 2005 21:12
Location: Denmark

Post by h4tt3n »

Great article! Settled a few Q's.

Cheers,
Michael
Post Reply