Structuring libraries, basic design tips.

Posted by on August 28, 2009

One of the most important things a programmer has to know and learn is proper code design, structuring of the code, etc. This may not be imperative for you if you’re just starting out, but sooner or later you’ll be able to spot the patterns and realize that your current way of doing things is not optimal but rather cumbersome. This is when you start to separate things into modules and you begin to design your code with more meaning and purpose.

By breaking your code into modules or “classes” you can have a finer granularity of the code itself, you can encapsulate the base code and work on top of it, you can also reuse the code in other projects if you have a good design. An example would be a log library (for logging useful debugging information onto text files) with proper design and care you’ll be able to use the same library in almost all of your projects with little to no modification at all.

The main points you should never forget about are:

  • Instancing
    • This is when you create an “object” – And as you create it, you should also be able to destroy it. (Constructors / Destructors in OOP). An object factory may be implemented (this would be another class) to handle the instances. In CPP you may use smart pointers, but we’re talking about PB in this case.
  • Members
    • In our case a main structure is used as the “members” (Again, in reference to OOP) – This structure is instantiated by the constructor or “create” function. Don’t forget that anything you allocate you should also be able to deallocate. It doesn’t matter if the language or OS handles it underneath; it’s bad coding practice to avoid such trivial – but important – code.
  • Methods
    • They give you functionality, in PB there are no methods but a similar concept applies: you have functions or routines that you create specifically for the library itself. Some may be of the Get / Set type (which either get or set a variable) Or other methods such as converters, helpers, accesibility calls, etc.

All together they make a class; in our case a library.

Let’s write one and follow by example!

Imagine you needed to encapsulate the random functions to provide extra functionality, then all you need to do is first define a structure (if you’re going to have members; for instance do you want the library to “remember” the previous and current numbers?, if so you would need a structure!)

;- Library Structure
Structure RANDOM
	LastNumber.i
	PreviousNumber.i
	Seed.i
EndStructure

Alright, now we need to write our constructor and destructor functions! This will create and destroy the instances.

;- Library Instancing
Procedure.i RandomCreate( Seed.i = 0 )
 
	Define.RANDOM *this = AllocateMemory( SizeOf( RANDOM ) )
 
	If *this
 
		If Not Seed
			Seed = ElapsedMilliseconds()
		EndIf
 
		With *this
			 \LastNumber 		= 0
			 \PreviousNumber 	= 0
			 \Seed 			= Seed
		EndWith
 
		RandomSeed( *this\Seed )
		ProcedureReturn *this
 
	EndIf
 
EndProcedure
 
Procedure.i RandomDestroy( *this.RANDOM )
 
	If *this
 
		FreeMemory( *this )
		*this = #Null
		ProcedureReturn *this
 
	EndIf
 
EndProcedure

That’s not too bad, first we allocate an instance and then we populate the member variables just to return the pointer of our new “object”. The destructor simply frees any allocated memory and clears out the pointer itself.

Now we need to define our library functions, in this case we only need one so we may generate a random number and keep track of the previous and current numbers.

;- Library functions
Procedure.i RandomGetInteger( *this.RANDOM, min.i, max.i )
 
	If *this
 
		*this\PreviousNumber 	= *this\LastNumber
		*this\LastNumber 	= ( min + Random( max-min ) )
		ProcedureReturn *this\LastNumber
 
	EndIf
 
EndProcedure

That’s ok, but we can still add functionality. For instance, we need a way to access the structure fields (or “members”) without having to cast pointers, etc. We can do this by implementing a few Get/Set routines. In this case we only need to get the previous and current numbers, so…

;- Helper functions
Procedure.i RandomGetLast( *this.RANDOM )
 
	If *this
		ProcedureReturn *this\LastNumber
	EndIf
 
EndProcedure
 
Procedure.i RandomGetPrevious( *this.RANDOM )
 
	If *this
		ProcedureReturn *this\PreviousNumber
	EndIf
 
EndProcedure

But wait, we can still add functionality! such as utility functions, in this case we would like to fill a given array with random numbers, so…

;- Utility functions
Procedure.i RandomFillArray( *this.RANDOM, min.i, max.i, Array DestArray.i(1) )
 
	If *this
 
		Define.i Size, i
		Size = ArraySize( DestArray(), 1 )
 
		If Size
			For i=0 To Size
				DestArray(i) = RandomGetInteger( *this, min, max )
			Next
		EndIf
 
	EndIf
 
EndProcedure

And while we’re at it, let’s fill lists as well!

Procedure.i RandomFillList( *this.RANDOM, min.i, max.i, elements.i, List DestList.i(), Clear.i=#False )
 
	If *this
 
		If Clear
			ClearList(DestList())
		EndIf
 
		Define.i i
 
		If elements
			For i=1 To elements
				If AddElement( DestList() )
					DestList() = RandomGetInteger( *this, min, max )
				EndIf
			Next
		EndIf
 
	EndIf
 
EndProcedure

If you don’t need to keep track of the current and previous numbers, you can just opt out the instancing system and modify the utility calls to work in an “instance-less” mode, however that’s up to you.

But, let’s take it for a ride first!

;- Example
Define.RANDOM *rnd = RandomCreate()
Define.i i
 
If *rnd
 
	For i=0 To 99
		Debug RandomGetInteger( *rnd, 5, 10 )
	Next
 
	Debug "--"
	Debug RandomGetLast( *rnd )
	Debug RandomGetPrevious( *rnd )
	Debug "--"
 
	Dim TestArray.i(9)
	RandomFillArray( *rnd, 100, 200, TestArray() )
 
	For i=0 To 9
		Debug TestArray(i)
	Next
 
	Debug "--"
 
	NewList TestList.i()
	RandomFillList( *rnd, 10, 20, 5, TestList() )
 
	ForEach TestList()
		Debug TestList()
	Next
 
	RandomDestroy(*rnd)
EndIf

Ok, so we tested the instancing and most if not all of the “methods” we just created. By now you should realize how useful such systems can be if you happen to design them correctly.

This particular lib could be extended to support averaging of the numbers, median / mean, etc. You could even implement a custom PRNG and give the option to choose between yours and the built-in one!.

You may download the entire source from here. As an exercise you could implement the Get/Set methods for the Seed.

Cheers!

PS: Someone remind me to update the language file for GeShi and to fix the font :) (The indentation is all wrong!)

2 Comments on Structuring libraries, basic design tips.

Respond | Trackback

  1. shadowsill says:

    it was a nice read. although the example was quite weird to me. why not a linkedlist or a stack?. I like how you emphasize the create & destroy deal (to avoid memory leaks).

    speaking of the devil, will you post a linked list library?. I read your other entries and I totally get you when you rant about dynamic lists. I too think it’s a must have and I can’t understand why they didn’t put them. although I disagree on PB being a shitty language.

    please add me to your messenger. I got a few questions about linkedlists.
    thanks!

    • GuShH says:

      Hi “shadowsill”

      I didn’t use a stack or list as an example because I wanted something even simpler to depict my words. And so it happens random numbers and a little bit of added functionality is not a bad example in my opinion.

      I do have a Linked List library, but it needs a little bit of refactoring and at the moment I don’t have the time, but I will eventually post most of my libs in here.

      Yes, It’s a shame that PB doesn’t support dynamic lists. Take a look at the Wikipedia entry on Linked Lists if you’re interested on implementing your own, it’s quite simple. However it takes some programming knowledge to do it efficiently. I think their team needs to get their priorities right; there is no use in faster code if you’re stuck with static lists altogether.

      I added you, but keep in mind that I may only reach you through offline messages most of the time, unless you happen to be online 24/7. I’m at -3 GMT here in Argentina, just so you know.

      Cheers.

Respond

Comments

Comments:

*