User defined types: |
The general syntax for declaring a user defined type is:
Type Typename Extends TypenameTypename must be a valid identifier.
The Extends part is optional. If omitted, the user defined type extends the built in Object type.
Once declared, you can create instances of such types using the New operator.
Within a user defined type, you can declare the following:
Type MyType Const INC=1 Global Counter Field x,y,z Method Sum() Counter=Counter+INC Return x+y+z End Method Function Create:MyType() Return New MyType End Function End Type Local MyObject:MyType=MyType.Create() MyObject.x=10 MyObject.y=20 MyObject.z=30 Print MyObject.Sum() Print MyType.CounterNote that the object is created indirectly by calling MyType's Create function instead of New. This is a frequently used technique that allows you to perform (possibly complex) initialization of an object before it is returned to the user.
User defined types can extend other user defined types using the Extends keyword. Extending a type means adding more functionality to an existing type. The type being extended is often referred to as the base type, while the resulting, extended type is often referred to as the derived type:
Type BaseType Field x,y,z Method Sum() Return x+y+z End Method End Type Type DerivedType Extends BaseType Field p,q,r Method Sum() Return x+y+z+p+q+r End Method End TypeThis technique is also known as inheritance, as the derived type is inheriting functionality from the base type (although no one had to die in the process!). Note that DerivedType actually has 6 fields - x,y,z,p,q and r. It inherits x,y,z from BaseType and adds its own fields p,q,r.
BlitzMax allows you to use a derived type object anywhere a base type object is expected. This works because a derived type object is a base type object - only with some 'extras'. For example, you can assign a derived type object to a base type variable, or pass a derived type object to a function expecting a base type parameter. This is really the whole point of inheritance - its not just a technique to save typing.
This behaviour allows for a very useful technique known as polymorphism. This means the ability of an object to behave in different ways depending on its type. This is achieved in Blitzmax by overriding methods.
Notice in the above example that the method 'Sum' has the same signature (parameters and return type) in both the base type and the derived type. This is not just a coincidence - it is required by the language. Whenever you add a method to a derived type with the same name as an existing method in a base type, it must have the same signature as the method in the base type.
But now we have 2 versions of 'Sum' - which gets called? This depends on the runtime type of an object. For example:
Type BaseType Method Test:String() Return "BaseType.Test" End Method End Type Type DerivedType Extends BaseType Method Test:String() Return "DerivedType.Test" End Method End Type Local x:BaseType=New BaseType Local y:BaseType=New DerivedType Print x.Test() 'prints "BaseType.Test" - x's runtime type is BaseType Print y.Test() 'prints "DerivedType.Test" - as y's runtime type is DerivedTypeNote that when the variable y is initialized, it is assigned a DerivedType object, even though y is a BaseType varible. This is legal because derived types can be used in place of base types. However, this means the runtime type of y is actually DerivedType. Therefore, when y.Test() is called, the DerivedType method Test() is called.
Self refers to the object associated with the method, and its type is that of the user defined type the method is declared in.
Super also refers to the object associated with the method, however its type is that of the user defined type being extended. This can be very useful if you need to call the base type version of the currently executing method:
Type BaseType Method Test() Print "BaseType.Test" End Method End Type Type DerivedType Extends BaseType Method Test() Super.Test 'calls BaseType's Test() method first! Print "DerivedType.Test" End Method End Type Local x:BaseType=New DerivedType x.Test
User defined types can optionally declare two special methods named New and Delete. Both methods must take no arguments, and any returned value is ignored.
The New method is called when an object is first created with the New operator. This allows you to perform extra initialization code.
The Delete method is called when an object is discarded by the memory manager. Note that critical shutdown operations such as closing files etc should not be placed in the Delete, as you can't always be sure when Delete will be called.
User defined types and methods can also be declared abstract or final by adding Abstract or Final to the appropriate declaration:
Type AbstractType Abstract Method AbstractMethod() Abstract End Type Type FinalType Final Method FinalMethod() Final Print "FinalType.FinalMethod" End Method End TypeDeclaring a user defined type abstract means that you can not create instances of it using New. However, it is still possible to extend such types and create instances of these derived types. Declaring a method abstract means that the method has no implementation and must be implemented by a derived type. Any user defined type with at least one abstract method is itself abstract.
Declaring a user defined type final means that it can not be extended. Declaring a method final means that derived types can not override the method. All methods of a final user defined type are themselves final.
Abstract types and methods are mostly used to create 'template' types and methods that leave implementation details up to derived types.
Final types and methods are mostly used to prevent modification to a type's behaviour.