Records & objects¶
When several values belong together, such as the X and Y of a point or the name and balance of an account, d/BASIC gives you two ways to group them: a TYPE, which is a record you can copy, and a CLASS, which is an object with an identity of its own. This page explains both, shows how objects are constructed and freed, how one class builds on another, how properties work, and how to choose between a record and a class. Choosing the right one is easier than memorizing either syntax:
A
TYPEis a value you can copy. ACLASSis an object with identity.
TYPE: a value record¶
TYPE Point
X AS INTEGER
Y AS INTEGER
END TYPE
DIM A AS Point, B AS Point
A.X = 3
A.Y = 4
B = A
B.X = 99
PRINT A.X ' 3: B is an independent copy
A record is stored inline, right where its variable is. Assigning one record to another copies its fields. A record has no constructor, no destructor, no identity, no virtual methods and no reference count; all of those belong to classes, described below.
Use a record for coordinates, colors, headers, small messages, dates, and any other data that should travel as a self-contained value.
Record fields¶
A record's fields can hold numbers, fixed-width strings and other records, and nothing else. A field cannot be an array, a variable-length STRING, a CLASS reference, a handle to a host resource such as an open file, or a WEAK link (explained below). A record may not contain itself, directly or through another record; the compiler stops and says so.
STRING * n declares a fixed-width text field inside a record; n can be anything from 1 through 65,535. Assigning text longer than n cuts it down to n bytes; assigning text shorter than n pads it with spaces; and every stored byte counts. The rule is deliberately exact:
DIM Card AS NameCard
Card.Name = "Alex"
PRINT LEN(Card.Name) ' 32
PRINT Card.Name = "Alex" ' 0: the padded field is not four bytes
PRINT RTRIM$(Card.Name) = "Alex" ' -1
An ordinary variable-length STRING holds only the bytes it needs, behind a counted reference, which is why it cannot sit inside a record's fixed layout. When a model needs variable-length strings, or anything else that works by reference, use a CLASS. The fixed-string rule is useful when a record must be stored or sent as an exact image of bytes, but it is also a trap for newcomers, worth learning once.
Arrays of records are ordinary arrays of values:
CLASS: identity and behavior¶
Here is the first object example, worth reading end to end:
CLASS Greeter
PRIVATE:
Name AS STRING
PUBLIC:
DECLARE CONSTRUCTOR (Initial AS STRING)
DECLARE SUB SayHello ()
END CLASS
CONSTRUCTOR Greeter (Initial AS STRING)
THIS.Name = Initial
END CONSTRUCTOR
SUB Greeter.SayHello ()
PRINT "HELLO, "; THIS.Name; "!"
END SUB
DIM G AS Greeter
G = NEW Greeter("ADA")
G.SayHello
G = NOTHING
Read it in four passes:
CLASSdeclares the shape of the object and which members callers may use.- The bodies of members live at module level, outside the class, under qualified names such as
Greeter.SayHello. NEWconstructs one object and returns a reference to it.NOTHINGreleases the variable's reference to the object.
Declaring a class variable does not create an object. The variable starts out as NOTHING; only NEW runs a constructor.
Identity changes assignment¶
A and B now refer to the same object. Change it through either name and the change shows through the other. Use A IS B to ask whether two names refer to the same object, and A IS NOTHING to ask whether a name is empty.
SET A = B is accepted for those used to older object-oriented BASIC dialects; plain A = B means the same thing when the destination is a class variable: it assigns the reference, not a copy.
Access sections¶
CLASS Account
PRIVATE:
Balance AS DECIMAL
PROTECTED:
Revision AS LONG
PUBLIC:
DECLARE FUNCTION CurrentBalance () AS DECIMAL
END CLASS
PUBLICmembers can be used by any caller.PROTECTEDmembers can be used inside the class and inside classes derived from it.PRIVATEmembers can be used only inside the class that declares them.
The compiler enforces these. They are part of the compiled class, not a convention your editor might or might not respect.
THIS¶
Inside a member body, THIS is the object the member was called on:
Writing THIS.Balance rather than plain Balance makes it clear which state belongs to the object, especially when a parameter has a similar name.
Constructors and destructors¶
A constructor runs when an object is created; a destructor runs when it is freed. A class can declare several constructors that take different parameters, and one destructor that takes none:
CLASS Note
PUBLIC:
Text AS STRING
DECLARE CONSTRUCTOR ()
DECLARE CONSTRUCTOR (Initial AS STRING)
DECLARE DESTRUCTOR
END CLASS
CONSTRUCTOR Note ()
THIS.Text = "UNTITLED"
END CONSTRUCTOR
CONSTRUCTOR Note (Initial AS STRING)
THIS.Text = Initial
END CONSTRUCTOR
DESTRUCTOR Note
' Release application-owned state here.
END DESTRUCTOR
d/BASIC frees objects by reference counting, and it does so at a predictable moment: when the last strong reference to an object is released, its destructor runs right then. There is no garbage collector that pauses the whole program.
Construction runs the base class first, then the derived class. Destruction runs the other way round: derived first, then base.
Inheritance and BASE¶
CLASS NamedThing
PROTECTED:
Name AS STRING
PUBLIC:
DECLARE CONSTRUCTOR (Value AS STRING)
DECLARE VIRTUAL FUNCTION Description$ ()
END CLASS
CLASS ButtonThing EXTENDS NamedThing
PUBLIC:
DECLARE CONSTRUCTOR (Value AS STRING)
DECLARE FUNCTION Description$ () OVERRIDE
END CLASS
CONSTRUCTOR NamedThing (Value AS STRING)
THIS.Name = Value
END CONSTRUCTOR
CONSTRUCTOR ButtonThing (Value AS STRING)
BASE(Value)
END CONSTRUCTOR
FUNCTION NamedThing.Description$ ()
Description$ = THIS.Name
END FUNCTION
FUNCTION ButtonThing.Description$ ()
Description$ = "BUTTON: " + THIS.Name
END FUNCTION
One class can build on another with EXTENDS, and a class has at most one base class. Inside the derived class's constructor, BASE(args) chooses which base constructor to run; leave it out and the base class's zero-argument constructor runs.
Virtual and abstract methods¶
A VIRTUAL method is one a derived class may replace with its own version. The derived class must mark that version OVERRIDE, so a misspelled name becomes a compile error instead of silently becoming a new, unrelated method.
DIM Item AS NamedThing
Item = NEW ButtonThing("SAVE")
PRINT Item.Description$() ' dispatches to ButtonThing
ABSTRACT declares a method that every derived class must supply and that the base class does not implement. A class with an abstract method describes what its derived classes promise to do, and you cannot create an object of it directly. Create a derived class that implements every abstract method, and make objects of that.
Properties¶
A property lets a read or a write look like a plain field while the work happens in explicit getter and setter bodies:
CLASS Meter
PRIVATE:
Stored AS INTEGER
PUBLIC:
DECLARE PROPERTY Value () AS INTEGER
DECLARE PROPERTY Value (NewValue AS INTEGER)
END CLASS
PROPERTY Meter.Value () AS INTEGER
Value = THIS.Stored
END PROPERTY
PROPERTY Meter.Value (NewValue AS INTEGER)
THIS.Stored = NewValue
END PROPERTY
DIM M AS Meter
M = NEW Meter()
M.Value = 12
PRINT M.Value
An indexed property takes its index parameters first and, for the setter, the new value last:
+= on a property evaluates the object, then the getter, then the right-hand side, then the setter, each exactly once and in that order.
Strong and weak relationships¶
An ordinary object field holds a strong reference: as long as the field points at an object, that object stays alive. A WEAK field points at an object without keeping it alive:
This is the classic tree pattern. A parent strongly owns its children; each child weakly points back at its parent. Once a weakly referenced object has been freed, reading the field gives NOTHING, never a reference to memory that is gone.
A class field may hold a number, a variable-length string, an inline TYPE record, or a strong or weak reference to an object. It may not directly hold an array, and it may not hold a handle to a host resource such as an open file or a network connection. Put a collection behind an object with its own methods, and let an object from d/Works, the office suite being written in d/BASIC (d/Write, d/Calc, d/Present), own the lifetime of any host resource on your behalf rather than storing a raw handle in your own object's state.
Static fields¶
A STATIC field belongs to the class as a whole rather than to any one object:
Use static state sparingly. State that lives in objects is easier to test, easier to combine, and easier to run in more than one window at once.
Objects as parameters¶
When an object is passed BYVAL, the procedure gets its own reference to the same object and cannot change which object the caller's variable names. When it is passed BYREF, the procedure can:
Either way, changes made to the object itself are visible through every name that refers to it. BYVAL versus BYREF decides whether the procedure may point the caller's variable at a different object; neither one makes a copy of the object.
The rule-of-thumb table¶
| Question | Choose TYPE |
Choose CLASS |
|---|---|---|
| Should assignment make an independent copy? | ✓ | |
| Does it need constructors or methods? | ✓ | |
| Does identity matter? | ✓ | |
| Is it a compact shape of data to send or store? | ✓ | |
| Does it own other objects, or manage the lifetime of a host resource? | ✓ | |
| Does it need inheritance or virtual methods? | ✓ |
When in doubt, begin with a TYPE. Promote it to a CLASS when identity or behavior becomes part of the problem, not merely because objects sound more modern.