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.
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.
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.