HTML5, CSS3, jQuery, JSON, Responsive Design...

Class Hierarchies in LotusScript - let’s loosen things up!

Michael Brown   September 6 2011 06:20:10 AM
Coming back to Object-Oriented programming in LotusScript after a lengthy spell programming in only JavaScript was a bit of a shock: LotusScript isso restricted!  Everything has to be declared and set up in rigid class hierarchies that can't be rejigged on the fly like you can in JavaScript (which has no real classes, of course).  And yet, with the judicious use of LotusScript's Variant type, it's possible to free yourself from some of these more obvious restrictions.


Problem - Class Hierarchies

One restriction that's always bugged me about LotusScript OO, is that the class libraries are a one-way door.  Class Library A can access the classes in Class Library B, or Class Library B can access teh classes in Class Library A.  But they can't both access each other's classes.  To do so would set up an infinite loop of Use statements, which the Domino Designer Compiler does not allow.

Here's an example of this problem.  I set up two classes in two separate LotusScript class libraries:  The Class CarClass is in the CarClasses library and the Class RoadClass is in the RoadClasses library.  Here's the code for the two classes:

' in CarClasses LotusScript libary
Public Class CarClass
            Private m_Name As String
            Private m_TopSpeedKMH As Integer
            Private m_Weight As String

            Sub new(n, k, w)
                            Me.m_Name = n
                            Me.m_TopSpeedKMH = k
                            Me.m_Weight = w

            End Sub

            Public Property Get theName As String
                            theName = Me.m_Name
            End Property

            Public Property Get topSpeedKMH As Integer
                            topSpeedKMH = Me.m_TopSpeedKMH
            End Property

            Public Property Get Weight As String
                            Weight = Me.m_Weight
            End Property

End Class



' in RoadClasses LotusScript library
Public Class RoadClass
            Private m_Name As String
            Private m_Length As String
            Private m_ChargeType As String

            Sub new(n, l, c)
                            Me.m_Name = n
                            Me.m_Length = l
                            Me.ChargeType = c

            End Sub

            Public Property Get theName As String
                            theName = Me.m_Name
            End Property

            Public Property Get theLength As String
                            theLength = Me.m_Length
            End Property

            Public Property Get chargeType As String
                            ChargeType = Me.m_ChargeType
            End Property

            Public Property Set chargeType As String
                            If chargeType <> "" Then
                                            Me.m_ChargeType = "Freeway"
                            Else
                                            chargeType = chargeType
                            End If
            End Property

End Class



And here's a test agent that creates object of both classes.  Obviously, that code must (and does) include Use statements that name the two libraries.

' Options section
Option Public
Option Declare
Use "RoadClasses"
Use "CarClasses"


Sub Initialize
            Dim myCarObj As New CarClass("Honda Jazz", 120, 100)
            Dim myRoadObj As New RoadClass("M4", 500, "Tollway")

            Messagebox "objects defined!"

End Sub




And here's how the objects look in the LotusScript Debugger when I've run my test agent:




So far, so easy.  Now I want to extend CarClass to include some info about the roads on which it can travel.  I do this by creating an array of RoadClass objects as a member of CarClass, remembering to add the line Use "RoadClasses" to the Options section of the CarClasses library, so that CarClass knows about RoadClass.

' In Options
Use "RoadClasses"

Public Class CarClass
            Private m_Name As String
            Private m_TopSpeedKMH As Integer
            Private m_Weight As String
            Private m_PermittedRoads() As RoadClass

            Sub new(n, k, w)
                            Me.m_Name = n
                            Me.m_TopSpeedKMH = k
                            Me.m_Weight = w
                            Redim Me.m_PermittedRoads(0)
            End Sub

            Public Function addRoad(newRoad As RoadClass)
                            Dim arraySize As Integer
                            If Me.m_PermittedRoads(0) Is Nothing Then
                                            Set Me.m_PermittedRoads(0) = newRoad
                            Else
                                            arraySize = Ubound(Me.m_PermittedRoads)
                                            Redim Preserve Me.m_PermittedRoads(arraySize + 1)
                                            Set Me.m_PermittedRoads(arraySize + 1) = newRoad

                            End If
            End Function

            Public Property Get theName As String
                            theName = Me.m_Name
            End Property

            Public Property Get topSpeedKMH As Integer
                            topSpeedKMH = Me.m_TopSpeedKMH
            End Property

            Public Property Get Weight As String
                            Weight = Me.m_Weight
            End Property

End Class



I then modify the test agent to pass some RoadClass objects into my CarClass object.  Here's how the code and its results look in the Domino Debugger:



You can see how the m_PermittedRoads property of the myCarObj contains an array of RoadClass objects, as we intended.  But what happens if we also want to do the reverse of this; i.e. I want my RoadClass object to have an array of CarClass objects, which are the types of car that are permitted to drive on my road?

Here's how I might try to set that up in CarClass class.  You can see that I've added a Use statement to pull in the CarClasses library.  I've then setup a class variable that is an array of CarClass objects.

' In Options
Use "CarClasses"

Public Class RoadClass
            Private m_Name As String
            Private m_Length As String
            Private m_ChargeType As String
            Private m_CarTypes() As CarClass
           ....


And here is where it all falls down.  The Domino Designer won't save this code; it will throw the error "RoadClasses (Options): 3: Error loading USE or USELSX module: CarClasses".

Why won't it load the "CarClasses"?  It's because the "CarClasses" library is already loading the "RoadClasses" library via its Use statement.  And as I said earlier, two LotusScript libraries cannot "load" each other.  That would put us you an infinite loop where CarClasses uses RoadClasses, which uses CarClasses, which uses RoadClasses .... and round, and round we go. Not suprising then that the Designer throws a wobbler.


Solution

One possible solution is to put both classes in the same library, but this leads to an inflexibility that might end up causing more problems than it solves.

My answer is to instantiate my class objects as type Variant, rather than as their actual class-types.  The Designer does not do compile-time type checking for Variants, so we don't then need to have each library contain a Use statement that tries to load up the other.  Only the calling code - our test agent in this case - needs to load up the two libraries.  This allows us to eat our cake and still have it.

The code below shows the changes: the m_PermittedRoads class variable and the newRoad parameter of the addRoad function are both now defined as Variants.  This means that we no longer have to include a Use "RoadClasses" line in the Options section of the library; we merely need have both libraries loaded in the calling agent, as we did previously.  The RoadClass objects are declared and instantiated in the calling test agent, and then passed into CarClass's addRoad method as a Variant.

I've also added a PermittedRoads Get Property so that we can access the variable later on and test what it is and what it can do.  Here's the modified CarClass library.  Note that the problematic Use "RoadClass" line is gone as we no longer need it.

Public Class CarClass
            Private m_Name As String
            Private m_TopSpeedKMH As Integer
            Private m_Weight As String
            Private m_PermittedRoads() As Variant

            Sub new(n, k, w)
                            Me.m_Name = n
                            Me.m_TopSpeedKMH = k
                            Me.m_Weight = w
                            Redim Me.m_PermittedRoads(0)
            End Sub

            Public Function addRoad(newRoad As Variant)
                            Dim arraySize As Integer
                            If Me.m_PermittedRoads(0) Is Nothing Then
                                            Set Me.m_PermittedRoads(0) = newRoad
                            Else
                                            arraySize = Ubound(Me.m_PermittedRoads
)
                                            Redim Preserve Me.m_PermittedRoads(
arraySize + 1)
                                            Set Me.m_PermittedRoads(arraySize + 1)
= newRoad
                            End If
            End Function

            Public Property Get theName As String
                            theName = Me.m_Name
            End Property

            Public Property Get topSpeedKMH As Integer
                            topSpeedKMH = Me.m_TopSpeedKMH
            End Property

            Public Property Get Weight As String
                            Weight = Me.m_Weight
            End Property

           Public Property Get PermittedRoads As Variant
                            PermittedRoads = Me.m_PermittedRoads
           End Property

End Class



Here's the modified calling agent as it appears on the LotusScript Debugger. I've called the new PermittedRoads property of myCarObj tor return the first of the RoadClass objects that I passed into it on the agent's two previous lines.  Although this object is returned as type Variant (as displayed in the Degugger), I can still access it's theName property, which proves that it's really a RoadClass object.




Comments

1Peter Presnell  09/06/2011 2:34:28 PM  Class Hierarchies in LotusScript - let’s loosen things up!

"One possible solution is to put both classes in the same library, but this leads to an inflexibiltiy that might end up causing more problems than it solves. "... I would suggest this is where the problem starts... The two classes are completely dependent on each other. These absolutely should be placed in the same libraries as you can't have one without the other. Having done that you no longer have any need to use variants as a workaround.

2Mike Brown  09/06/2011 3:16:00 PM  Class Hierarchies in LotusScript - let’s loosen things up!

@Peter,

Perhaps that's true for the two classes that I've listed above. But then I only knocked them together to demonstrate the technique.

I am already using the technique for real live situations where it is not practical to lump all the classes into the same library. I may write a follow-up to show these.

3Lars Berntrop-Bos  09/13/2011 8:42:35 AM  Class Hierarchies in LotusScript - let’s loosen things up!

Or you could put the main stuff apart, and in a third library which includes the other two write a new clas which is a subclass of one and includes the other.

that way, the mixed class stuff is put in a separate space, even less lumping!

About