Login   /   Register

Factory Pattern

Rate this article
     1 votes, average: 5 out of 51 votes, average: 5 out of 51 votes, average: 5 out of 51 votes, average: 5 out of 51 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. It would also be prudent to brush up on the Singleton Pattern.

We continue our tour of Design Patterns with another basic pattern – the Factory (or Abstract Factory). Like the Singleton Pattern, the Factory Pattern is a creational pattern, which means it is used to create or initialize a certain object. As you might have understood by its name, this pattern’s implementation can act as a factory, which takes in manufacturing instructions, and spits out the relevant product (or object).

As with all the design patterns we will cover, this too will be mutated to fit into the world of VBScript and QTP. The solution I’ll present and its implementations will be a synthesis of the Factory method, the Abstract Factory method, and some VBScript class techniques. So if you’re familiar with Factory/Abstract Factory solutions from object-oriented languages, prepare to see something new. 

A Little Help from a Genie

The simplest implementation of the Factory Pattern is the Genie. This is because much like a Genie, we just have to tell it what object we need, and it will magically conjure it up. A custom object repository provides a solid example. It will combine the benefits of "naming" objects (like the QTP native OR), with the benefits of using Descriptive Programming.

We could easily construct such an object repository with an "Object Genie", as discussed in a previous article and originally presented here. The Genie will serve us the relevant object corresponding to the logical name we provide it.

Function ObjectGenie(sObjectName)
    Select Case sObjectName
        Case "Save Button"
            Set ObjectGenie = VBWindow("vbname:=X").VBButton("text:=Save")
        Case "Main Window"
            Set ObjectGenie = VBWindow("vbname:=X")
        Case Else
            Set ObjectGenie = Nothing
    End Select
End Function
 
ObjectGenie("Save Button").Click 'Clicks the save button

This is a very naïve and simplified object Genie, and a real one would probably include delegation to several sub-functions, building the object only at the end of the function, and wrapping it with a meta-data shell. Though it’s no required in order to understand the Factory Pattern, a more complex example for an object Genie implementation is available here, if you’re interested.

As we’ve seen, the object Genie takes in manufacturing instructions (e.g. the object logical name), and spits out the corresponding product (e.g. the actual QTP object).  A lot of the everyday mechanisms in QTP and VBScript are in fact implementations of the Object Genie principle – and by extension of the Factory Pattern. If we think about it, the native QTP OR act very much like our custom object Genie (though it’s far more elaborate); and the ever popular CreateObject command embodies the very essence of the Genie principle.

Set oWord = CreateObject("Word.Application")

Here CreateObject is the Object Genie / Factory, "Word.Application" is the manufacturing instruction, and the oWord object is the final product. For all its directness and simplicity, the CreateObject command is an extremely flexible factory implementation, as it can create all the COM objects known in your system.

The Benefits of Remembrance

These Genie examples have some virtues – they are easy to implement and maintain, and provide a central access point for multiple object creation. However, they have one major drawback – they don’t remember the objects they’ve created. This is exactly the problem that the Singleton Pattern was set to solve, but it was built to manage the resources of only a single object.

Let’s see if we can combine the multiple object creation capabilities of the Factory Pattern, with the object resource management capabilities of the Singleton Pattern.

We’ve seen how a single excel object is managed via the Singleton Pattern. Now, say we don’t just want to write our data to one excel file, but three; or maybe a combination of excel file and text files; or some files and some DB connections. While these objects differ in detail, they are similar in the logical role they play in our script – outputting data. If we just wrap each of them in a Singleton class, or use a different global variable for each of them, we miss some of this logical similarity between them.

It seems that we need a central authority to create, store and manage these output mechanisms. This can be easily constructed using a Scripting.Dictionary storage with a class wrapper. The class will have a .Construct method (the Object Genie), which will create a new output channel of the requested type, and add it to the storage with a unique identifier. The new channel can be recalled later using the identifier. You could say it’s the old object Genie, with a new twist.

'Provide a global access point
Public oOutputs
 
Set oOutputs = New clsOutputFactory
 
Class clsOutputFactory
    Public Channels 'Stores the output channel
 
    Private Sub Class_Initialize
        Set Me.Channels = CreateObject("Scripting.Dictionary")
    End Sub
 
    Private Sub Class_Terminate
        Set Me.Channels = Nothing
    End Sub
 
    Public Sub Construct (sChannelName, sChannelType)
        Dim oNewChannel
    
        Select Case sChannelType
            Case "Excel"
                'Create a new excel channel class
                Set oNewChannel = New ExcelChannelClass
            Case "Text"
                'Text init code here
            Case "DB"
                'DB init code here
        End Select
    
        'Add the new channel to the storage
        Me.Channels.Add sChannelName, oNewChannel
    End Sub
 
End Class

And now we can add the required channels at the beginning of our script

oOutputs.Construct "Errors", "Excel"
 
oOutputs.Construct "Backup", "Text"
 
oOutputs.Construct "Another output log", "Text"
 

And use them throughout our script

'Imagine our excel class wrapper has a .Write method
oOutputs.Channels("Errors").Write "Something"
 

This mechanism achieves several goals. It puts an end to the global variable inflation – instead of dozens separate global variables, we only need the one to access our channel storage; Moreover, the number of output channels is dynamic, and could change from script to script and over time (with zero maintenance).  It centralizes the object creation process to one main Factory, which serves all the output needs for the script.

One major benefit of centralizing our object creation and remembering what we’ve create is the ability to add layers of validation and business logic to the process. For instance, we could check to see if the requested channel was already created, and act accordingly:

Public Sub Construct (sChannelName, sChannelType)
    Dim oNewChannel
    
    If Me.Channels.Exist(sChannelName) Then Exit Sub
 
    Select Case sChannelType
        'Continue code…
End Sub
 

Of course this is a very basic validation – usually we would implement a much more sophisticated mechanism. For example, we can add a validation of the file’s name / DB table name to make sure you don’t overwrite our own outputs. We can pool our DB connections in a much more sophisticated and efficient manner than the one made possible by using only the Singleton Pattern. The possibilities are endless.

Centralizing Global Actions

We’ve just seen how a centralize storage can offer plenty of opportunities to make our code tidier and more efficient. However, there is more to come - by remembering the objects we’ve created, we can perform global actions on all of them at once. For example, we may want to output some data through all our channels – text, excel and DB.

One way of doing that is to separately send the output to each and every channel, which can become quite a headache once the number of channels will inflate. Another way is to add an .OutputAll method to our storage, which will loop through all the channels that factory has created, and send the output to each and every one of them.

Sub OutputAll(sData)
    Dim arrKeys
    
    arrKeys = Me.Channels.Keys
 
    For I = 0 To Me.Channels.Count – 1
        Me.Channels(arrKeys(i)).Write sData
    Next
End Sub
 

While this technique is not directly related to the pure Factory Pattern, the only way to implement it in VBScript is by coupling it with the object creation process controlled by the Factory. Of course that much like the possibility to apply validations during the object’s creation (Singleton validations or others), we can apply validations for our global commands. For instance, we can choose to output the data only to excel channels, only to channels that output to files on drive C, or only to channels which have to word "Error" in their logical name.

Assigning Responsibilities and Polymorphism

In order for our global commands to work, we must make sure that all the objects we’ve created support these methods and sub-procedures. So for example, if we use the command Me.Channels(arrKeys(i)).Write sData, it would be a shame if that specific channel didn’t have the .Write method, as it will result in a script error.

This also applies for any validation layer we’d like to add to our global commands – after all, validating that the channel is an excel channel, or that it writes to a file on drive C must rely on the channel’s object to supply that information. The object must support some .Type property (to return "Excel"), and some .OutputLocaction property (to return "c:\log.txt") in order for our validation to work.

This is where Polymorphism comes into play. Wikipedia defines polymorphism as a feature that allows values of different data types handled using a uniform interface. Simply put, it means that we can have many different types of output channels objects, but they all must support a common interface (i.e. method or property) - .Write. Once we know all our relevant objects support this interface, we can use a global command such as Me.Channels(arrKeys(i)).Write sData, knowing that no matter what exact channel is Channels(arrKeys(i)), it will have a.Write command, which takes in a single parameter.

The same goes for our extra validation layers. If we want to use a global command which operates only on output channels of a certain type, we must make the channel’s type information available through a uniform interface (e.g. the .Type property). Another option is to make the factory responsible for that piece of information (as opposed to the channel objects themselves). After all, when we created the channel, we knew its type (our .Construct method received it in the sChannelType parameter), so we could’ve recorded the type, and use it in our global validations, without bothering the channel objects themselves with providing that information.

There are many aspects to consider when deciding whose responsibility is it to provide certain pieces of information. Some code design principles call for the channel objects to be the sole source of information about themselves - this way they become more "complete" and encapsulated. Others point out that the responsibility for carrying out global actions belongs to the factory, and not the separate objects – and therefore only the factory should be responsible for that information. Theory aside, you should probably see what arrangement simplifies your scripts, and go with that.

Blind Factories

If you still remember, our original channel .Construct method used a Select-Case structure to understand which channel object it should create, according to the string parameter the user had passed to it. This seems unavoidable – we must keep a complete list of all the possible objects to create somewhere. Or must we? It would be awfully nice to rid ourselves of the maintenance headache in updating the list whenever we want to add a new type of object.

Well, it seems that even VBScript can provide was with a loophole to avoid this maintenance overhead. We can make out factory "blind" – that is, try to dynamically extract the name of the relevant object class and try to actually create it. This is done with the VBScript Execute command:

Public Sub Construct (sChannelName, sChannelType)
    Dim oNewChannel
 
    Execute "Set oNewChannel = New " & sChannelType
    'Continue code
 

The execute command takes the dynamic string "Set oNewChannel = New " & sChannelType, and executes it as native VBScript code. This means that if we send "Excel" as the sChannelType parameter, the command that will actually be executed would be:

Set oNewChannel = New Excel

In the worst-case scenario, the user will send a string that doesn’t correspond to any known object or class. Our script will simply produce an error, which can be easily dealt with via error handling techniques. This means that if we create a new type of output channel, our factory will be able to work with it immediately, out-of-the-box, with zero-maintenance. This is very similar to the CreateObject command, which can work with any COM object "out-of-the-box", and just pop-up an error if the user attempts to create a nonexistent object type.

Notice, however, that when we turn our factory blind, we relinquish the power to store any extra-data about the objects we create within the factory itself (as it has no familiarity with the object it creates). This means that if we want to implement global commands such as .Write, and validations such as writing only to excel channels, we must implement then via very strong polymorphism in the created objects themselves. This limits the strength of our "out-of-the-box" feature – the factory works fine, as long as the requested object supports all the interfaces needed for the factory’s global commands and procedures.

An Actual Implementation

After all this tiresome theory, we can now take a closer look at real-world example. The ReporterManager Project is in fact just an extensive implementation of the Factory Pattern. Let’s examine some of its key features. These are just specific code sections from the relevant methods and classes. For the complete code, please download the ReporterManager Project, it is open-source and well documented.

First of all, a little background for those of you who are unfamiliar with the ReporterManager project. ReporterManager takes care of the reporting aspect of QTP scripting, by making it extremely easy to report events to Excel, text, windows native event log, DB and other logs. It allows you to set up as many reporters as you would like, and funnel different events to each one according to preset rules and filters.

For example, you can set up a text backup log styled for convenient reading; a special text Error log (which only includes failed steps) styled for quick skimming (+save pictures of all the errors); and a fancy Excel log, which only logs performance timers. And you can do all that with just 10 code lines.

As I’ve said, the heart of the ReporterManager mechanism is just an implementation of a Factory Pattern. It is somewhat unique in that the created objects (i.e. the different reporters) aren’t accessible directly. You can only operate them via the factory’s wrapper and methods. So if a usual Factory Pattern would handle the objects’ creation process, and let you do whatever you want from there on, the ReporterManager forces you to go through the factory for your everyday interactions with the created objects (like reporting events, shutting them down etc.).

The manager (i.e. factory) has a .StartEngine method which is used to create new reporters (i.e. output channel) and add them to the reporters’ collection. The method receives three parameters – sType (the type of the reporter), sName (the logical name by which it can be used) and sSettings (not relevant for our example). Here’s the actual creation code from that method:

On Error Resume Next
    'Try to create an instance of the reporter engine’s class
    Execute "Set oReporter = New " & sType & "Engine"
    
    If Err.Number <> 0 Then
        bResult = False
        Reporter.ReportEvent micFail, " StartEngine", "Failed to " & 
             _ "create instance : " & sType
    End If
On Error Goto 0
 

As you can see, the ReporterManager is a blind factory. The .StartEngine method receives the desired reporter type to create into the sType parameter (e.g. "Excel"), and attempts to create an object of type sType & "Engine" (e.g. "ExcelEngine"). The whole section is wrapped in an error handler, in case the desired class doesn’t exist. This means that if a new reporter type would be created, the ReporterManager code could create and use it right out-of-the-box.

After the new reporter was created, initialized and stored in the oReporter variable, our factory conducts some important validations. For example, it checks if there already exists another reporter with the same logical name. If so, the old reporter is stopped, and the new one put in its place; if not, the new reporter added to the manager’s reporter’s collection. This is a simplified version of the code, but it still demonstrates the point:

If oReporters.Exists(sName) Then 'The name is unique
    StopEngine(sName) 
    Set oReporters(sName) = oReporter 
Else
   oReporters.Add sName, oReporter 
End If

The specifics of the code don’t really matter, but there is something worth noticing. On the one hand, the ReporterManager is a blind factory, which needs to execute certain global commands (e.g. StartEngine, Report etc.). In order to do so, it must rely on the reporter class polymorphism, and specifically in this code snippet, on the old reporter supporting the .StopEnigne procedure (which simply activates a .StopEngine procedure within the reporter class itself). On the other hand, the ReporterManager does know something about the objects it creates (e.g. their logical name), and is therefore able to execute at least some form of validation by itself (e.g. check whether a reporter with that name already exists).

After we’ve created the reporters, we can now use them throughout our script. This is done by calling the .Report command of the ReporterManager. This command just loops through the reporters’ collection, and activates the .Report command in each and every one of them. Again, the ReporterManager counts on the different reporter objects polymorphism, i.e. that they all indeed support such a command with the specific parameters the ReporterManager passes them. This code snippet is a simplified version of the actual code, but it still demonstrates the point:

Public Sub Report(sStatus, sStepName, sExpected, sActual, sDetails)
    Dim i
    Dim arrKeys
 
   arrKeys = oReporters.Keys 'Get all reporter names
 
    For i = 0To oReporters.Count-1
        Call oReporters(arrKeys(i)).Report (sStatus, sStepName, sExpected, sActual, sDetails)
    Next
 
End Sub
 

If you want to learn more about Factories, global commands and polymorphism, I suggest you learn ReporterManager Project in depth, as it presents an extensive and elaborate use of these techniques. 

Summary

We’ve seen how Factories (and Genies) can be used to further centralize our control over the objects we create and maintain in our scripts. While the Singleton Pattern provided us with the means to manage a few objects and their control their resources, the Factory Pattern allows us to multiply the concept to a dynamically growing number of objects, as well as to add an extra layer of validation to the process.

However, the implications of the Factory Pattern are much more profound. The ReporterManager implementation demonstrates how a central point of access to a storage of objects allows us to execute global commands on all/some of them. This ability can play a critical role in making our scripts simple and powerful, as one command can have a global effect on a wide array of objects.

Posted in Design Patterns

One Response to “Factory Pattern”

  1. polymorphism allows for specifics to be dealt with during Says:

    [-]

    […] […]

Leave a Reply

You must be logged in to post a comment.

This article was viewed 1233 times