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