Skip to content

LotusScript Classes Deep Dive Part Two

In the last part I covered the basics of what constitutes a Class in LotusScript / VoltScript. I also said that user-defined classes in LotusScript are often avoided because developers can interact with their data via the platform classes in lsxbe (Notes...) and lsxui (NotesUI...). When classes are used in proprietary applications they may typically be very straightforward - standalone classes with no inheritance. But there is much more possible, as Java developers will be aware of.

Interfaces and Abstract Classes

Java developers will be aware of interfaces and abstract classes. Neither can be used directly by code, the compiler forces the developer to create a class that implements an interface or extends an abstract class.

Interfaces can only contain variable declarations with a value defined. In older versions of Java, interfaces could not contain code, they could only contain method declarations - the method, its scope, the parameters it takes and its return type. Since Java 8 interfaces can also contain default implementations of methods.

On the contrary, Abstract Classes can contain variable declarations. It can contain method declarations, if the methods are also declared as abstract, or it can contain actual method implementations.

LotusScript / VoltScript

LotusScript and VoltScript have a simpler structure, with just classes. But there are steps a developer can take to follow the design patterns from Java, although probably not regularly used.

Subs and functions are automatically default implementations. However, it's also possible to have a sub or function without any implementation using the Declare keyword.

Class Animal
    Declare Function makeNoise() as String
End Class

Developers who have used On Disk Projects will be familiar with this, because the IDE automatically generates the forward declarations for Classes, Subs and Functions in Script Libraries. But developers can add their own Declare statements without actual implementations of the functions. This doesn't directly reproduce the functionality of interfaces, because neither the IDE nor the compiler force implementations of these declarations in sub-classes. Also, the IDE and runtime will allow different signatures for the same function name in a sub-class. So a sub-class can have Function makeNoise(noise as String) as String. But it can be used to indicate expected functions and subs for sub-classes, whether that's for developers looking at the class itself or in documentation, if you have tooling that automatically generates documentation for your classes.

When it comes to constructors or abstract functions, it's not possible to generate compiler errors if sub-classes do not provide implementations. However, it is not difficult to generate run-time errors.

Sub New()
    Error 1501, "Not Implemented"
End Sub

This is a technique I've used, the error number echoing the HTTP Status 501, Not Implemented. This should certainly highlight during development if the developer has not implemented a function that needs to be implemented.

Base and Derived Classes

When you're building classes for an application, there may be little benefit in creating classes that depend on one another. But when you're building tooling or open source projects, there are more reasons to build classes that are intended to be sub-classed. And when it comes to sub-classes in LotusScript / VoltScript, there are a few points to bear in mind.

Firstly, if the base class - the class you are sub-classing - is written in C/C++ in an LSX, you cannot create a derived class from it. This is why you cannot create classes that extend e.g. NotesDocument, part of lsxbe, which was the first LSX. You can only create a derived class in the same language as the base class. So a class written in an LSX can be sub-classed in an LSX, a class written in LotusScript can be sub-classed in LotusScript, but a class in an LSX cannot be sub-classed in LotusScript.

Secondly, in LotusScript / VoltScript functions and subs in a derived class must have the same signature as in the derived class. They must have the same parameters in the same order, and return the same type of variable. This should not be a surprise, bearing in mind function names cannot be overloaded. The New sub is an exception, as we will see shortly.

Constructors

Creating the derived class is straightforward, Class DerivedClass as BaseClass. And if you don't need to call the base class's constructor, creating the constructor is also straightforward, exactly the same creating a normal constructor. But sometimes you may want to call the base class's constructor. Then the syntax gets a little more sophisticated.

Class Animal
    Private name as String

    Sub New(animalName as String)
        Me.name = animalName
    End Sub

End Class

Class Dog as Animal

    Sub New(petName as String), Animal(petName)

    End Sub

End Class

Dim dog as New Dog("Baxter") will pass the petName "Baxter" down to the Animal class's constructor, passing it to the Animal's name variable. (Of course, in a real use case, name would be a public property with just a Get.)

Calling Base Functions

The other slightly unusual syntactical approach is interacting with the base class's properties and functions / subs. Using BaseClass..subName, BaseClass..functionName or BaseClass..propertyName you can call subs, functions and properties of the base class from the derived class. The particularly useful part here is that this syntax can be used to call Private properties or functions from the derived class. So this is possible.

Class Animal
    Private name as String
    Private owner as String

    Sub New(animalName as String)
        Me.name = animalName
    End Sub

    Private Property Set ownerName as String
        Me.owner = ownerName
    End Property

    Public Function getOwner() as String
        getOwner = Me.owner
    End Function

End Class

Class Dog as Animal

    Sub New(petName as String, ownerName as String), Animal(petName)
        Animal..ownerName = ownerName
    End Sub

End Class

Note here the constructor for the Dog class takes two parameters, the pet's name and the owner's name. But the Animal class only takes a single parameter, the animal name. The New Dog constructor first creates an instance, setting the name property. It then calls the base Animal class's ownerName property setter. Even though that property is private in the base class, it is still accessible from the derived class. This may overcome the need for the Protected or Friend keywords in some circumstances.

One caveat to point out is that this syntax cannot be used to interact with private variables in the base class. Private variables are private to the base class and inaccessible outside. But even still, this provides a great degree of flexibility of base and derived classes.