Abstract singletons in LotusScript
Category domino
Two recent blog entries caught my attention: one from Julian about creating singletons, and one from Chad about simulating abstract classes. To summarize each:
Julian revealed that, while private classes cannot normally be accessed outside of the same scope, a public instance of a private class within the same scope declared as a Variant allows access to all of its members from outside that scope. He demonstrated that singletons can be created by defining a public factory class in the same script library as a private class; no matter how many times the factory class is used to return the private class, only a single instance is created. Read the full post for details on why this can come in handy.
Chad explained that, while LotusScript does not support true abstract classes, they can be simulated by declaring an instance of a class and giving it the same name as the class. To be honest, I was initially surprised this was allowed... but after giving it more thought, it makes sense: if LotusScript supported abstract classes and you gave a variable the same name as a class, the compiler would be unable to tell if later references to the same class were referring to the class itself or the specific instance of it by the same name. But because it doesn't, there's no confusion. He mentioned that there's no way of enforcing this, however.
But... based on Julian's findings, it occurred to me that simply making the "abstract" class private and declaring the public instance of it as a Variant would allow you to do just that: hence, abstract singletons: classes that you can refer to by their class name without having to first instantiate with another variable - 'cause the class is already loaded just by loading the library - and can't create more than one instance of - 'cause they're private. The one downside I can see to this is that any class treated this way would be automatically loaded into memory whether you need it or not (as long as it's within scope, at least), but the impact of this can be minimized by preventing its members from being initialized until they're needed. Here's how:
1. Declare a private Boolean as a member of the class, and set it to False in the constructor:
2. Create a subroutine to initialize any members that would otherwise consume additional memory to be called if needed - in other words, everything you'd normally put in the constructor:
3. Create a subroutine to check the Boolean and initialize the members if it's still False:
This is actually something that I've wanted for a very long time. Over the last five years, I've been accumulating a library of dozens of frequently used functions and classes. This library is now shared among approximately 250 applications, and although it saves me a huge amount of time and keeps my code relatively clean, there's still a lot of unnecessary duplication. Sure, I've occasionally gone through and simplified some of the functions that do similar things by adding a new function to do what each of them have in common, then replacing the common code in each with calls to the new function. But nobody has time to be constantly refactoring their own code, and nearly all code I write needs the current session, database, document, etc. So I added a System class to the library today that obtains all of these commonly needed objects, and after only about a half hour of refactoring, the library now does everything it did before... but with about half of the code.
Two recent blog entries caught my attention: one from Julian about creating singletons, and one from Chad about simulating abstract classes. To summarize each:
Julian revealed that, while private classes cannot normally be accessed outside of the same scope, a public instance of a private class within the same scope declared as a Variant allows access to all of its members from outside that scope. He demonstrated that singletons can be created by defining a public factory class in the same script library as a private class; no matter how many times the factory class is used to return the private class, only a single instance is created. Read the full post for details on why this can come in handy.
Chad explained that, while LotusScript does not support true abstract classes, they can be simulated by declaring an instance of a class and giving it the same name as the class. To be honest, I was initially surprised this was allowed... but after giving it more thought, it makes sense: if LotusScript supported abstract classes and you gave a variable the same name as a class, the compiler would be unable to tell if later references to the same class were referring to the class itself or the specific instance of it by the same name. But because it doesn't, there's no confusion. He mentioned that there's no way of enforcing this, however.
But... based on Julian's findings, it occurred to me that simply making the "abstract" class private and declaring the public instance of it as a Variant would allow you to do just that: hence, abstract singletons: classes that you can refer to by their class name without having to first instantiate with another variable - 'cause the class is already loaded just by loading the library - and can't create more than one instance of - 'cause they're private. The one downside I can see to this is that any class treated this way would be automatically loaded into memory whether you need it or not (as long as it's within scope, at least), but the impact of this can be minimized by preventing its members from being initialized until they're needed. Here's how:
1. Declare a private Boolean as a member of the class, and set it to False in the constructor:
Private Class SystemThe Boolean becomes a flag that keeps track of whether or not the other members have been initialized, and as the only member initialized prior to the class being accessed, causes the class to only consume 10 bytes of memory (8 for the Variant, 2 for the Boolean) in modules where the class is loaded but never used. Paltry overhead so far...
Private boolInitialized As Boolean
'Declaration of other members would also be listed here
Public Sub New()
Let boolInitialized = False
End Sub
2. Create a subroutine to initialize any members that would otherwise consume additional memory to be called if needed - in other words, everything you'd normally put in the constructor:
Private Sub intializeMembers( )Setting the Boolean to true allows you to ensure this subroutine is only called once.
Set nsesCurrent = New NotesSession
If Not (nsesCurrent.IsOnServer) Then'Skip workspace initialization if the code is running on a server because NotesUIWorkspace would be unavailable
Set nuiwsCurrent = New NotesUIWorkspace
End If
Set ndbCurrent = nsesCurrent.CurrentDatabase
Set ndocContext = nsesCurrent.DocumentContext
Set nnamCurrent = New NotesName(nsesCurrent.EffectiveUserName)
Let boolInitialized = True
End Sub
3. Create a subroutine to check the Boolean and initialize the members if it's still False:
Private Sub confirmInitialization()4. Finally, in any property or method that would need the other members to have previously been initialized, insert a call to confirmInitialization() before the remainder of the code:
If Not (boolInitialized) Then
Call intializeMembers()
End If
End Sub
Public Property Get ThisDatabase As NotesDatabaseThis way none of the members are initialized if the class is never accessed, but as soon as it is, they're ready. In any code using the library containing this class, you would then be able to replace, for example, all of the following:
Call confirmInitialization()
Set ThisDatabase = ndbCurrent
End Property
Dim nsesCurrent As New NotesSessionwith:
Dim ndbCurrent As NotesDatabase
Set ndbCurrent = nsesCurrent.CurrentDatabase
Print ndbCurrent.Title
Print System.ThisDatabase.Title
This is actually something that I've wanted for a very long time. Over the last five years, I've been accumulating a library of dozens of frequently used functions and classes. This library is now shared among approximately 250 applications, and although it saves me a huge amount of time and keeps my code relatively clean, there's still a lot of unnecessary duplication. Sure, I've occasionally gone through and simplified some of the functions that do similar things by adding a new function to do what each of them have in common, then replacing the common code in each with calls to the new function. But nobody has time to be constantly refactoring their own code, and nearly all code I write needs the current session, database, document, etc. So I added a System class to the library today that obtains all of these commonly needed objects, and after only about a half hour of refactoring, the library now does everything it did before... but with about half of the code.