Login   /   Register

Singleton Pattern

Rate this article
     2 votes, average: 5 out of 52 votes, average: 5 out of 52 votes, average: 5 out of 52 votes, average: 5 out of 52 votes, average: 5 out of 5
Loading ... Loading ...
March 29th, 2008 by Yaron Assa

This article refers to design patterns, pointers and objects (discussed in the background section of this article), and classes. Make sure you’re familiar with these issues before reading on.

We begin our exploration of the word of Design Patterns with one of the simplest patterns – the Singleton. The Gang of Four eloquently summarize the intent of the Singleton pattern as follows: "Ensure a class only has one instance, and provide a global point of access to it". We can illustrate the pattern’s concept with an example.

Background and motivation

Imagine having a script which accesses a database, or writes data to an external excel file. In order to accomplish these tasks, the script must create and utilize auxiliary objects that connects to the database or the file (e.g. a FileSystemObject, an Excel.Application or an ADO object), and perform various tasks.

If we allow our script to create these objects freely, we risk ending up with extremely inefficient code, or even worse, a script which produces errors and even breaks the system it tests. Just imagine the time wasted by creating an Excel COM object every time you report an event, or creating multiple database connection to the application’s backbone. These objects might even lock the relevant resources, thereby failing the script in unpredictable ways.

So it’s clear we should control the manner in which certain objects are created and managed throughout our scripts. This is exactly the context in which the Singleton pattern operates – it allows us to freely ask for these objects, but ensures that only one central instance of each object will be actually created and used.

Constraints and workarounds

As I’ve stated in the introduction article, Design Patterns are "meant for" object oriented programming languages. As a result, we’ll have to "downgrade" the patterns to fit into a VBScript and QTP context. As we’re about to see, this "downgrading" will be especially dominant for the Singleton pattern.

"Real" programming languages have static (AKA shared) variables and sub-procedures, and they play a critical part in any implementation of the Singleton Pattern. In case you’re unfamiliar with the terms – don’t worry. VBScript doesn’t support static elements anyway (neither does JavaScript), so we’ll have to find some workaround that will allow us to implement the Singleton pattern in VBScript without them.

I will present several implementations of the Singleton pattern, with growing complexity and usability. Some implementations may fit a certain situation better than others (not necessarily the most sophisticated ones), but they all track the same logical frame of mind.

VBScript implementations

The most basic VBScript implementation of this pattern relays on a global variable which holds the required object (it serves as a degraded form of static variable). New objects are created only if the global variable is "empty".

Public oExcel 'Announce the global variable
Dim bAleadyInit 'Will tell us if the object requires initialization
 
'Check if the object is already initialized
bAlreadyInit = False
 
If IsObject(oExcel) = True Then
    'The object was once initialized
    If Not oExcel is Nothing Then
        'The object is "alive"
          bAlreadyInit = True
    End If
End If
 
'Create a new instance only if needed
If bAlreadyInit = False then Set oExcel = CreateObject("Excel.Application")
 
oExcel.Whatever 'Use the object

Now, this will work fine, but will produce tons and tons of duplicate code. So the next step will be to pack the duplicate code in a dedicated Sub-procedure:

Sub InitExcel
    Dim bAleadyInit 'Will tell us if the object requires initialization
    bAlreadyInit = False
 
    If IsObject(oExcel) = True Then
        'The object was once initialized
        If Not oExcel is Nothing Then
            'The object is "alive"
             bAlreadyInit = True
        End If
    End If
 
    'Create a new instance only if needed
    If bAlreadyInit = False Then Set oExcel = CreateObject("Excel.Application")
End Sub
 
InitExcel 'Initialize the object (if needed)
oExcel.Whatever 'Use the object

These implementations will work, but they are poorly designed. The code is too coupled – both the "client" (i.e. the flat code) and the procedure relay on the global variable, which means that if we ever change the global variable name or implementation we’ll be in deep trouble (it will force us to run through the code and correct all the oExcel reference). In order to solve this we could change our procedure to a function, and return a direct reference to the initialized object. This way the strong-coupling is broken, and the client (i.e. the flat code) doesn’t care where the reference originated.

Function GetExcel
    Dim bAleadyInit 'Will tell us if the object requires initialization
    bAlreadyInit = False
    
    If IsObject(oExcel) = True Then
        'The object was once initialized
        If Not oExcel is Nothing Then
            'The object is "alive"
             bAlreadyInit = True
        End If
    End If
 
    'Create a new instance only if needed
    If bAlreadyInit = False Then Set oExcel = CreateObject("Excel.Application")
 
    'Return a reference to the Singleton object
    Set GetExcel = oExcel
End Function
 
Set oExcelInstace = GetExcel 'Get the object
oExcelInstace.Whatever 'Use the object

But wait, what about all the little oExcelInstace variables we’ll create throughout our scripts? It seems that the new design creates many duplicate instances of the excel object. However, we must remember that these are just pointer-reference to a single excel object, which is only created once. They cost us near-to-no resources, and we can easily eliminate them at will. Even if we destroy all these pointers, the real excel object will endure as it is still referenced by our global oExcel variable:

Set oExcelInstace = GetExcel 'Get the object
oExcelInstace.Whatever 'Use the object
Set oExcelInstace = Nothing 'Destroy the pointer; the real object lives on

Singleton and wrapper classes

Up until now we’ve separated our objects from the Singleton mechanism. We had an excel object, and a separated function which took care of the excel object’s lifecycle for us (or functions if we want another function in charge of destroying the Singleton object at the end of the script). While this design has its benefits, it overlooks the fact that we usually wouldn’t want to use separate functions for managing and utilizing our auxiliary objects.

Imaging we had separate functions for writing to a certain sheet of an excel file, for reading data, for querying specific values, etc. Our code will scatter and lose cohesion over time. It will become un-encapsulated, messy and hard to read and maintain. Usually, a good solution for this problem would be writing a wrapper class that encapsulates all the relevant code for an object. Here’s an example for a skeleton to a wrapper class of an excel object:

Class ExcelWrapper
    Private oExcelObject
 
    Private Sub Class_Initialize
        Set oExcelObject = CreateObject("Excel.Application")
    End Sub
 
    Private Sub Class_Terminate
          oExcelObject.Quit
        Set oExcelObject = Nothing
    End Sub
 
    Public Sub Open(sFileName)
        'Code for opening the file
    End Sub
 
    Public Function GetSheetData(sSheet)
        'Code for reading the sheet and returning it as an array
    End Sub
    
    'More functions that manipulate and use the object
End Class

We can now modify our wrapper to implement the Singleton pattern by changing the Class_Initialize procedure:

Class Excel
 
    'Other class methods and variables
    Private Sub Class_Initialize
        Dim bAlreadyInit
 
        bAlreadyInit = False
        If IsObject(oExcel) = True Then
            'The object was once initialized
            If Not oExcel is Nothing Then
                'The object has not been destroyed
                bAlreadyInit = True
            End If
        End If
        
        'Only create new object if needed
        If bAlreadyInit = False Then Set oExcel = CreateObject("Excel.Application")
 
        'Set the local class excel reference to the global Singleton object
        Set oExcelObject = oExcel
    End Sub
End Class
 
Set oExcelInstace = New ExcelWrapper
oExcelInstace.Open("Something")

We can create as many instances of the wrapper class as we’d like – they will all access a single excel object. In some cases the wrapper could be even more efficient – for example, if we always access the same file and sheet, the wrapper could open them in the class initialization phase. In which case, once we create a new instance of the wrapper, we know it’s immediately ready for read/write operations.

Since we want to encapsulate everything into our wrapper classes, we must implement a method that destroys the global excel object (as destroying the wrapper class only destroys the pointers to the global object). This method should be called only when the script ends, and the auxiliary object is no longer needed (i.e. not in the Class_Terminate procedure):

Class Excel
    'Other class methods and variables
 
    Public Sub Destroy
      oExcel.Quit
      Set oExcel = Nothing
    End Sub
End Class

A QTP tweak

Shifting our focus from VBScript to QTP, we can replace the global variable with a better mechanism by using the QTP Environment object. Instead of polluting our code with unnecessary global variables, we can just use the following object creation code (either in a wrapper class or a function):

Dim bAleadyInit 'Will tell us if the object requires initialization
On Error Resume Next
    bAlreadyInit = IsObject(Environment("Excel_Object"))
    If Err.Number <> 0 Then bAlreadyInit = False 'Environment isn’t even initialized
On Error Goto 0
 
If bAlreadyInit = True Then
    If Environment("Excel_Object") is Nothing Then bAlreadyInit = False
End If
 
If bAlreadyInit = False Then
   Environment("Excel_Object") = CreateObject("Excel.Application")
End If

Conclusion and prelude to the Factory Pattern

The Singleton pattern can help manage the pesky details of our objects’ lifecycles. The motivation for using it is clear - there are situations in which unmanaged creation of objects can lead to script failures and application problems.

While the Singleton pattern is considered to be a simple pattern, its VBScript implementation tends to be complicated since the language lacks static operations support. I hope that presenting the different implementations in a progressive manner helped you to understand the motives and purposes in each implementation, and to track the pattern’s relatively simple logic, regardless of its technical complexities.

The next article in the series will explore one of the more powerful Design Patterns – the Factory Pattern. We will show how we can combine it with the Singleton pattern to further centralize our control over the script’s entities and objects.

As we’ll add more patterns to our toolbox, our power and understanding will grow, and we’ll be able to present far more realistic and meaningful examples. You’ll see that the combined power of the Singleton and Factory patterns is so great, that it will enable us unravel the heart of the ReporterManager project.

Posted in Design Patterns

5 Responses to “Singleton Pattern”

  1. Factory Pattern | BETA Says:

    [+]

    [...] This article refers to design patterns, pointers and objects (discussed in the background section of this article), and clas... ...

  2. QTP: Synchronization for AJAX Applications | Relevant Codes Says:

    [-]

    […] Singleton Pattern, by Yaron Assa […]

  3. QTP: Working with Multiple Browser Applications - A Concept (by Anshoo Arora) | Relevant Codes Says:

    [-]

    […] Singleton Pattern by Yaron Assa (AdvancedQTP, SolmarKN) […]

  4. qqchuck Says:

    [+]

    In listing 3, you don't show the declaration of the "global". Can't you make it private? i.e. Private oExcel 'Announce the global... ...

  5. QTP: Working with Multiple Browser Applications (Revised) | Relevant Codes by Anshoo Arora Says:

    [+]

    [...] 1. An Improved Dictionary Object by Yaron Assa (AdvancedQTP, SolmarKN) 2. Singleton Pattern by Yaron Assa (AdvancedQTP, [...... ...

Leave a Reply

You must be logged in to post a comment.

This article was viewed 1716 times