This is a very simple primer for a QM Class called "Lst". This class is a very simple set of routines designed to manage related lists of data in BASIC. The goal of the Lst Class is to conveniently store and retrieve sets of data keyed by a name. Each set can contain an associated value, plus an optional list of tag=tagvalue elements. This class was designed to facilitate inter-process messaging and connectivity to JMS (Java Messaging Service), but is a useful storage class in itself.
The 'Lst' Class is nothing special. It is not built into QM. It is just a collection of application code like any other cataloged application subroutine or function.
In non-OO code, you could implement a sub-set of the Lst Class functionality with a subroutine that looked something like:
SUBROUTINE LstManager(cmd, name, val) COMMON /LstManager/ Names, Values begin case case cmd = 'init' Names = '' Values = '' case cmd = 'set'
locate(name,Names;l) else l = dcount(l,@am)+1 Names<l> = name
Values<l> = val
case cmd = 'get'
locate(name,names;l) then
val = Values<l>
end else
val = '' end case cmd = 'delete' locate(name,Names;l) then Names = delete(Names,l,0,0) Values = delete(Values,l,0,0) end case cmd = 'list' val = Names end case return
This is a very simple subroutine that will let you keep a list of values associated with names. Because of how this is written, there are some limitations that your application needs to be concerned with:
This is a really useful subroutine, and using it is fairly straight-forward.
CALL LstManager('init','','') CALL LstManager('add','Name','George') CALL LstManager('add','Addr','100 Main Street') ... CALL LstManager('list','',lst) for i = 1 to dcount(lst,@am) CALL LstManager('get',lst<i>,val) display lst<i> : ' -> ' : val next i
While this is easy enough to use, the syntax is still somewhat verbose. So, what would the calling program look like with objects:
lst = object('Lst') lst->val('Name') = 'George' lst->val('Addr') = '100 Main Street' ... lst = lst->lst for i = 1 to dcount(lst,@am) display lst<i> : ' -> ' : lst->val(lst<i>) next i
The OO version of this function has a number of characteristics:
If the 'Lst Class' is written well, the limitations of the subroutine are also mitigated.
Of course, the subroutine could get these features as well. Making the subroutine case insensitive is pretty trivial. Setting up the subroutine to allow for binary storage is somewhat harder. Allowing for multiple lists, and even lists of lists, gets a lot harder. With objects you can even do:
... lst2 = object('Lst') lst2->val('Item1') = '00001' lst2->val('Item2') = '00002' lst->val('SubList') = lst2
So now lists can contain lists. Lists can actually contain any data that fits in a variable, so this includes strings, objects, and even things like file handles. But what does it take to make the Lst Class actually work.
'Lst' Class formal property/method list:
Classes are usually documented "from the outside". It is important that applications know how to use a class. How the class actually works internally is generally more nebulous. A well designed set of public properties and methods for a class will hide the inner working of the class allowing for modifications to the class's inner workings without requiring changes to the calling programs.
Init obj->InitMethod Initialize the list to empty val obj->val(name) = value result = obj->val(name)Read/Write Property to reference a list element delete obj->delete(name) Method Method to remove a name from the list lst result = obj->lstRead only Retrieves a list of all names as a dynamic array tag obj->tag(name,tagname) = value
result = obj->tag(name,tagname)Read/Write Property to reference a tag value for a particular name DelTag obj->DelTag(name,tagname)Method Method to delete a tag from a name element TagLst result = obj->TagLst(name)Read only Retrieve a list of all tag names for a particular name str obj->str = value result = obj->strRead/Write Property that converts the entire array to and from a single packed string. copy obj->copy(obj2)Method Method to copy one list collection into another.
In QM, Classes are defined in a QM/Basic source code item in the same way that you would write a subroutine. In fact, the similarities between Classes and subroutines extends down to the run-time Basic level itself. Classes are compiled and cataloged just like subroutines. They have names just like subroutines, and their names must be unique, not only against other Classes, but against other cataloged programs and subroutines as well.
'Lst' Class source code:
Class LstAll Classes start with a "CLASS" directive, just like a subroutine or function. $catalog localClasses have to be cataloged. In this case, we decide to catalog this class 'locally'. You could also catalog this class as a 'regular' catalog item, or 'globally'. * PUBLIC PublicVariableThis is where you can define public variables. This particular class definitions does not have any public variables.
Public variables can be used directly from any program that has an object variable.
obj->PublicVariable = 'Hello' display obj->PublicVariable PRIVATE names
PRIVATE tags
PRIVATE valsThese are private variables. They will persist for as long as the object exists. If you create several instances of the same class:
obj1 = object('Lst') obj2 = object('Lst')then each object "instance" will get it's own private variables.
Private variables cannot be used outside of the class but are available to all code inside of the class.
PUBLIC SUBROUTINE Init names = '' tags = '' vals = object('Array') endWe want a routine that will erase all data. This would be called as:
obj->InitNote that 'vals' is actually an Array object.
PUBLIC SUBROUTINE CREATE.OBJECT me->Init endThe behaviour of PUBLIC and PRIVATE variables is to initialize as zeros. In this case we want them to initialize with nulls. We just setup the CREATE.OBJECT built-in subroutine to just call our own Init routine. PUBLIC SUBROUTINE DESTROY.OBJECT endThere is also a built-in subroutine that is called when the object is destroyed. We don't need this with Lst, so we can put in a blank routine, or just leave it out all together. GET Val(name) if name = '' then return('') locate(downcase(name),Names;l) then
return(vals->val(l+2))
end else return('')
end
endThis is code that retrieves a named value. We store names in a dynamic array and use a simple locate statement to look them up. Values are stored in a dimensioned array so there are no limits to the type of data that you can store. The dimensioned array will also be much faster if the collection gets large.
display obj->Val('name')The value itself is stored in the array object in position l+2. The offset of 2 allows us to use the Array->str property to convert a list to a single string with minimal overhead.
SET Val(name,val) if name = '' then return locate(downcase(name),mames;l) then
vals->val(l+2) = val end else locate('',names;l) else l = dcount(names,@am) + 1 end names<l> = downcase(name) tags<l> = '' vals->val(l+2) = val end endThis is code that sets a value. The values themselves are stored in a dimensioned array and this dimensioned array may need to be extended as it's size grows. We extend it in blocks of 20 just to help performance out a little bit.
If we wanted this object to perform well even with thousands (or millions) of names and values, we could create and open a temporary file and store the data there. This would limit the class to storing only string data, but would make it useful even for very large amounts of data.
obj->Val('name') = 'Some data' PUBLIC SUBROUTINE Delete(name) locate(downcase(name),Names;l) then Names<l> = '' tags<l> = '' vals->val(l+2) = '' end endThis is code that will delete a name/value pair. We don't compress the arrays, but just leave blank holes. The 'SET Val' routine is designed to re-use these holes, so compressing the value list would not help much anyway.
obj->Delete('name') PUBLIC FUNCTION Lst res = Names loop locate('',res;l) then res = delete(res,l,0,0) end else exit end repeat return(res) endThis is code that will retrieve a list of names as a dynamic array. In most cases this will be a simple assignment, but deleted names can leave holes that need to be removed.
lst = obj->Lst for i = 1 to dcount(lst,@am) display lst<i> : '=' : obj->Val(lst<i>) next i GET Tag(name,tag) if name = '' then return('') if tag = '' then return('') locate(downcase(name),names;l) then locate(downcase(tag),tags,1,l;ll) then return(tags<l,2,ll>) end end return('') endThis is code that will retrieve a tag value associated with a particular list element name
display obj->tag('name','tp')Note that in this implementation, tag values must be simple strings and cannot contain system delimiters.
SET Tag(name,tag,val) if name = '' then return('') if tag = '' then return('') if val = '' then return('') locate(downcase(name),names;l) then locate(downcase(tag),tags,1,l;ll) then tags<l,2,ll> = val end else tags<l,1,-1> = tag tags<l,2,-1> = val end endThis is code that will set a tag value associated with a particular list element name
obj->tag('name','tp') = 'text' GET TagLst(name) if name = '' then return('') locate(downcase(name,names;l) then return(change(tags<l,1>,@svm,@am) end return('') endYou can also retrieve a list of all tag names associated with a list element. GET str vals->val(1) = names vals->val(2) = tags return(vals->str) endThe 'str' property provides a way to take a collection of names and values and save them to a single string. This does have some limitations in that it assumes that the values are all strings and don't contain things like file handles or object handles. The strings are binary clean though, so you can have collections of entire database records and even host binary records like images or application data files.
Uses for this string are up to the application. You might write it to a file for another process to read, transmit it over a network connection, use it as a calling parameter to a subroutine, or whatever.
Because the values are already stored in an Array object, where we have left the first two elements open, we can easily use the Array Object's str property directly.
write obj->str on temp.file , 'temp.id' SET str(str) vals->str = str names = vals->val(1) tags = vals->val(2) endThis SET property lets you restore a state that was previously saved.
read str from temp.file , 'temp.id' then obj->str = str end PUBLIC SUBROUTINE copy(lst2) me->Init names2 = lst2->lst for i = 1 to dcount(names2,@am) name = names2<i> me->val(name) = lst2->val(name) tags2 = lst2->TagLst(name) for j = 1 to dcount(tags2,@am) tag = tags2<j> me->tag(name,tag) = lst2->tag(name,tag) next j next i endAnd just for convenience, we will provide a copy method so that you can duplicate one list object into another. end... and all programs end with an end