OOP Guidelines

BlitzMax Forums/BlitzMax Tutorials/OOP Guidelines

Bot Builder(Posted 2005) [#1]
After seeing several mis-applications of oop, I've decided to write a small tutorial about how to use oop to properly create types.

Naming
Types
For type naming I dont really have a standard. It is most natural to name the type something like "Car" or "House", but the standard used in the standard modules "TBank" or "TList" is very nice since you can have an instance called "Bank" or "List". It's your decision.

Elements
For elements (fields, globals, constants, functions, methods), just have the name of the element. Nowhere in the element should there be the type name or an abreviation of the type name.

Bad:
Type TCar
 Field CarSpeed
 Field CarWidth
 Field 
 Method DriveCar()
  'BLAH
 EndMethod

 Method HonkHorn()
  'BLAH
 EndMethod
EndType
Good:
Type TCar
 Field Speed
 Field Width

 Method Drive()
  'BLAH
 EndMethod

 Method HonkHorn()
  'BLAH
 EndMethod
EndType
This is often a problem when migrating to an OOP language from a procedural language like all previous versions of blitz.

Grouping Globals, Constants, and Fields
You should group similar elements of the same element type that are on the same line. For instance:
Before:
 Field X#
 Field Y#
 Field Z#
After:
 Field X#,Y#,Z#
You can also group groups of similar elements. For instance:

Before:
 Field X#,Y#,Z#
 Field Speed
 Field Width#, Height#, Depth#
After:
 Field X#,Y#,Z#
 Field Width#, Height#, Depth#
 Field Speed


Properties
Properties are times when you have some fields that when affected or even the value is retrieved, code is executed, fields change. Although blitzmax doesn't support actual C#/Java properties, they can still be done in a way with two methods. Also, properties are most useful if you make the actual field private so the user cannot edit it. Sadly, BlitzMax does not support this either. MARK SIBLY > ADD PRIVATE ELEMENTS AND OVERLOADS + MAYBE PROPERTIES If not I might add them to my preprocessor if it gets off the ground ;)

Okay, so properties using current BMAX stuff:
Type Car
 Field Speed#
 Field MaxSpeed#

 Method SetSpeed(Value#)
  If Value#>MaxSpeed# Then Speed#=MaxSpeed# Elseif Value#<0 Then Speed#=0 Else Speed#=Value#  'Range Check
 End Method

 Method GetSpeed#()
  Return Speed#
 EndMethod
EndType
Some OOP guidelines for languages without properties suggest making all of your fields private and creating methods like this for all of them (sometimes one set per group). This allows you to later on add code that executes when a field is changed, while maintaining backward compatability. This is what C#/Java properties have solved. You can leave all of your fields as fields, and later on convert them to properties, allowing you to execute code, maintain backwards compatability, have sleek field style access, without bloated classes.

Inheritance
I'm going to assume you read Mark Sibly's Intro to OO. Inheritance shouldn't be applied to every situation in which you have the same fields / functions / methods / constants / globals / methods. When this statement is true - "<Derived> is a type of <Base>", you should use inheritance. As far as I know BMax doesn't have multiple inheritance. This is no problem because in most cases you only want single inheritance, and multiple inheritance is misuse. There are however some rare situations where a type is a type of two other types (tounge twister here), where those other types are not types of eachother. Anyway, If you have multiple types with similar elements, however they are not types of a type defined by those elements, create your type and include it as a field in all the other types. For instance:
Type Vector
 Field X#,Y#
End Type

Type SpaceObject Abstract
 Field Location:Vector  'Strictly not a vector, but hey
 Field Speed:Vector

 Method Update()
  Location.X:+Speed.X
  Location.Y:+Speed.Y
 EndMethod
End Type

Type Ship Extends SpaceObject Abstract 'Ship is a type of space object
 Field StartHealth#=0, StartArmor#=0
 Field Health#, Armor#
End Type

Type Cruiser Extends Ship 'Cruiser is a type of ship
 Field StartHealth#=200, StartArmor#=20
End Type
Abstraction
Ok, really this should be renamed templation or something, the keyword being "template". However, this is not so, yet.

Anyway, abstract should only be used when the inheritance rule of above applies, and also, when you will never actually want to create a base class. At this point you make it abstract. Be sure to make the classes deriving from it final unless you want to have even further inheritance. One application worthy of abstract is creating a module in which one class can do many different things depending on how applied. Basically, a class for handling similar stuff (TStreamFactory), when the actual implementation is totally different (TRamStreamFactory).


Duckstab[o](Posted 2005) [#2]
Type Vector
 Field X#=67,Y#=54
End Type

Type SpaceObject Abstract
 Field Location:vector= New Vector  'Strictly not a vector, but hey
 Field Speed:Vector= New Vector

 Method Update()
  Location.X:+Speed.X
  Location.Y:+Speed.Y
  Print location.x
  Print location.y
 EndMethod
End Type

Type Ship Extends SpaceObject Abstract 'Ship is a type of space object
 Field StartHealth#=0, StartArmor#=0


 Field Health#, Armor#
End Type

Type Cruiser Extends Ship 'Cruiser is a type of ship
 Field StartHealth#=200, StartArmor#=20
End Type

a:Cruiser=New Cruiser

a.Update()



just incase anybody problems with the last method you have to add the New ect Command aswell


JetFireDX(Posted 2005) [#3]
Types and methods are good. Thanks for this!


WendellM(Posted 2005) [#4]
Good stuff, Bot Builder.

I agree with you about the importance of privacy; I'm experimenting with starting all of my "private" Field names with an underscore ("public" ones, if any, don't have this). The same goes for any Functions or Methods intended to be private within a Type. Since I think that starting names with underscores is hideously ugly, I'm motivated never to use them outside of Type/End Type blocks and to use Methods to set and retrieve private Field values :).

Also, by putting Consts inside of types, enumerated types can be simulated (at least until actually added) and kept separate. So, with:
Strict

Type TStockMarket

	Const LOW    = 1
	Const MEDIUM = 2
	Const HIGH   = 3

	Field _Confidence
	
	Method SetConfidence( n )
		Assert ( n >= LOW ) And ( n <= HIGH ), "Confidence out of range"
		_Confidence = n
	End Method

	Method GetConfidence()
		Return _Confidence
	End Method
	
End Type



Type TRadio

	Const VERYLOW  = 1
	Const LOW      = 2
	Const MEDIUM   = 3
	Const HIGH     = 4
	Const VERYHIGH = 5

	Field _Volume
	
	Method SetVolume( n )
		Assert ( n >= VERYLOW ) And ( n <= VERYHIGH ), "Volume out of range"
		_Volume = n
	End Method

	Method GetVolume()
		Return _Volume
	End Method
	
End Type


Local DowJones:TStockMarket = New TStockMarket
DowJones.SetConfidence DowJones.HIGH
If DowJones.GetConfidence() = DowJones.HIGH Then Print "Buy!"
Delay 1000

Local Radio:TRadio = New TRadio
Radio.SetVolume Radio.HIGH
If Radio.GetVolume() = Radio.HIGH Then Print "That's kind of loud!"
Delay 1000

Radio.SetVolume( 0 ) 'out of range error
there's no confusion between the stock market's HIGH and the radio's HIGH.