Class Composition
Posted by admin - Mar 29, 2008 Articles, Code Design, Yaron Assa 0 0 Views : 621 Receive Updates For This Category
Article Tools
- Print this page
- Add Comment
- Send to Friend
- Last Updated on :
Jul 15, 2011
This article deals with classes and their instances in VBScript. Make sure you’re familiar with the subject by reading the introduction article to classes.
In this article we’ll cover the basics for composing classes (i.e. using one class from within another), and some common and less common uses for this technique. I’m sure that once you’ll see the huge benefits we can gain by selectively using this technique, you’ll at least try it.
[hideit]
How it’s done
The technical side of class composition is quite simple and easy. We just create a reference to a class within another class:
Class clsCaller
Private Phone 'Will hold a clsPhone instance
Private Sub Class_Initialize
Set Phone = New clsPhone 'Create instance
Phone.Number = "902359454235"
End Sub
Private Sub Class_Terminate
Set Phone = Nothing 'Destroy instance
End Sub
Public Sub Dial
Call Phone.Dial 'Use the clsPhone class instance
End Sub
End Class
Class clsPhone
Public Number
Public Sub Dial
MsgBox("Dialed" & Number)
End Sub
End Class
This is a classic case of class composition, where the technique is used to enhance one of our classes (the host class), by letting it use the power of a secondary, utility class (the composed class).
As you can see, we’ve strengthen our host class (clsCaller), by composing the clsPhone utility class to it. This allows us to use oCaller.Dial functionality , without getting our main class messy and bloated with code.
The above code also demonstrates a typical composed class lifecycle. It’s created and destroyed together with the hosting class (since it’s located within the Class_Initialize and Class_Terminate event-subs). However, this is not always the case.
Sometimes a composed class instance will be created and destroyed many times without the hosting class changing at all. For example, if our composed class takes care of writing to a text file, we might like to not lock the file for the entire time, but only when we actually touch it. One way of doing this is to destroy the composed utility class, and re-create it only when needed (although there’re better ways – but for the example’s sake).
A collection of instances
A very popular use of class composition involves using dictionary collections of composed classes. Taking the previous example a step further and say we need to dial multiple numbers. We could use a collection of Phone instances, each initialized to its own number. This way we allow the user to dynamically add more dialers as he sees fit. This requires us to change our clsCaller class:
Class clsCaller
Private oPhones 'Will hold the collection of clsPhone instances
Public Sub AddPhone(sNumber)
Dim oNewPhone
Set oNewPhone = New clsPhone
oNewPhone.sNumber= sNumber
oPhones.Add oPhones.Count, oNewPhone
Set oNewPhone = Nothing
End Sub
Private Sub Class_Initialize
Set oPhones = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate
Set oPhones = Nothing 'Destroy instance
End Sub
Public Sub Dial
Dim i
'Loop through all phones and dial them
For I = 0 to oPhones.Count - 1
Call oPhones(i).Dial
Next
End Sub
End Class
Now the user can add as many phones as he’d like:
Set oCaller = New clsCaller
Call oCaller.AddPhone("NumberA")
Call oCaller.AddPhone("NumberB")
Call oCaller.Dial
As you can see, the dictionary collection usage is very simple, and it allows us to store and use potentially unlimited amount of class instances. This approach has only one downside – when our clsCaller class is terminated, we nullify the phones collection (by the Set oPhones = Nothing command).
However, when done this way, the clsPhone instances we’ve created will sill take up memory and resources (and potentially lock up files and create other sorts of problems). In order to make sure they are completely destroyed, we need to loop through the collection, and set each one of them to nothing. Well see how this is done in the next example.
Fake self-reference and tree structures
A class can also reference itself. This is actually very common in tree structures, where every node (a node class instance) holds a reference to its parent-node, and its child-nodes. It’s extremely important to understand that this doesn’t create any self-reference issues, since each node is a different class instance. It’s similar to running several process of Word – though they’re all Word applications, each one of them is unique and separate from the others.
The following code snippet shows how a tree structure can be managed and created through class composition. An actual tree implementation will be much more sophisticated (for starters, it would hide its tree structure implementation), but will operate on the same principles
Class clsTreeNode
Public Parent 'Will hold the parent-node
Public Children 'A dictionary collection of child-nodes
Public ID 'A string ID
'Adds a new child to the current tree node
Public Sub AttachChild(oNewChild)
Set oNewChild.Parent = Me
Children.Add Children.Count, oNewChild
End Sub
Private Sub Class_Initialize
Set Children = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate
Dim i
'Nullify all child-nodes and remove from the children collection
For I = 0 to Children.Count - 1
Set Children(i) = Nothing
Children.Remove(i)
Next
Set Children = Nothing
End Sub
End Class
Set oRoot = New clsTreeNode
oRoot.ID = "Root"
Set oNewChild = New clsTreeNode
oNewChild.ID = "Child1"
oRoot.AttachChild oNewChild
Set oNewChild = New clsTreeNode
oNewChild.ID = "Child2"
oRoot.AttachChild oNewChild
Set oNewChild = New clsTreeNode
oNewChild.ID = "GrandChild1"
oRoot.Children(0).AttachChild oNewChild
Set oNewChild = Nothing
'Climbing down the tree
Msgbox oRoot.Children(0).Children(0).ID ‘Will print "GrandChild1"
There are a few things worth noting in this example:
When the script reaches the final line, the ONLY clsTreeNode instance available from the public general code is oRoot. All other instances are hidden and are inaccessible.
When the Script ends, each object in the public general code area is destroyed. Since the only clsTreeNode instance there is oRoot, we must provide a mechanism for it to property destroy all the other nodes in the tree. This is done via the Class_Terminate event-sub, which destroy each of the root’s children. This in turn sets up a chain reaction (since the children now fire their Class_Terminate event-subs), which recursively destroys every single node in the tree.
In the AttachChild procedure, the current tree node sets a reference to itself within the new child instance (within the child’s parent property). This is a very common thing to do (not only in tree structures) – when a new class instance is created, it’s unfamiliar with its environment and specifically its host class (since the host class holds a one-directional reference to it). A lot of times the host class immediately sets a reference back to itself, to allow the composed class to know its place.
As you’ve probably understood by now, there is not practical limitation on the amount of class instances you can compose, and the depth of the tree you can build (There is a memory limitation, but you have to create over 0.5 million instances of big classes to reach it, so generally speaking, we’re in the clear).
Trees and inner-searches
Tree structures are incredibly powerful. Imagine we add the following methods to our clsTreeNode class. They allow us to gain instant access to any node in the tree, without knowing the exact path-drilldown which has led to it.
'Returns the tree node with the ID = sID
Public Function GetLeaf(sID)
Dim oRoot
'Go back to the root
Set oRoot = Me
While oRoot.ID <> "Root"
'As long as we’re not in the root, keep climbing up
Set oRoot = oRoot.Parent
Wend
'Activate downward recursive search
Set GetLeaf = SearchChildren(oRoot, sID)
End Function
'Searches recursively downwards for the relevant level
Private Function SearchChildren(oLevel, sID)
Dim oResult
Dim i
If oLevel.ID = sID Then
Set SearchChildren = oLevel ‘We’ve Found the level
Exit Function
End If
'If the current level isn’t the correct one, maybe one of it’s children
Set oResult = Nothing
For I = 0 to oLevel.Children.Count -1
'Do the recursive search for each of the children
Set oResult = SearchChildren (oLevel.Children(i), sID)
If Not oResult Is Nothing Then Exit For ‘We’ve found our level!
Next
Set SearchChildren = oResult
End Function
Now we could execute this from the public code area (I use oRoot.Children(0) and not just oRoot to show we can initiate a search from any tree node):
Msgbox oRoot.Children(0).GetLeaf("GrandChild1").ID 'Will print out "GrandChild1"
These are just simple example which could be turned into much more powerful and sophisticated tree search and navigation function, but the main thing is to get the point. Class composition allows us to quickly run through the class reference chain, until we reach the relevant node. Because classes can be as rich with properties as we’d like them to be, we can set up signs and signals that can help us get meaningful results (e.g. ID property, or any other meaningful property we’d like to search).
Composition and forward extendibility
(The following example is a simplified version of the new FrameworkManager project which will be released in a while)
Let’s imagine we have a host class which is in charge of inputting QTP field object (e.g. VBEdit, VBCombobox etc.). Since we would like our main class to remain as simple and clean as possible, we won’t put the input code within an ugly Select-Case structure for the type of the field. Instead, we decide to delegate the actual input code to small utility classes such as these:
Class clsInputText
Public Sub Input(oQTPObject, sValue)
oQTPObject.Set sValue
End Sub
End Class
Class clsInputCombo
Public Sub Input(oQTPObject, sValue)
oQTPObject.Select sValue
End Sub
End Class
And now our main host class can be very simple and elegant:
Class clsInputThings
Private oQTPObject 'Lets pretend this holds the QTP object
Private oInputClass 'This holds either clsInputCombo or clsInputText
Public Sub Input(sValue)
Call oInputClass.Input(oQTPObject, sValue)
End Sub
End Class
However, how can we know which input class to compose to our main class? It seems that no matter what we try, an ugly Select-Case structure will have to emerge.
'This goes inside clsInputThings
Public Sub Input(sValue)
Select Case oQTPObject.GetROProperty("micclass")
Case "VBCombobox"
Set oInputClass = new clsInputCombo
Case "VBEdit"
Set oInputClass = new clsInputText
End Select
Call oInputClass.Input(oQTPObject, sValue)
End Sub
And this kinda defies the entire point. However, not all is lost – there’s a neat trick which allows use to dynamically create classes without knowing exactly what they are.
First, let’s change the names of the utility classes. clsInputText will become clsInputVBEdit, and clsInputCombo will become clsInputVBCombobox. Now we can extract the class of the QTP object and dynamically build the name of the utility class from it. After that’s done, we just need to use the Execute command to turn our string into actual script code:
Public Sub Input(sValue)
Dim sCreateClass
sCreateClass = "Set oInputClass = New clsInput"
sCreateClass = sCreateClass & oQTPObject.GetROProperty("micclass")
Execute sCreateClass 'Will create the relevant class
Call oInputClass.Input(oQTPObject, sValue)
End Sub
In the real world we would have error handles etc., but that’s not really important for our example.
As you can see, we don’t hard-code the utility class names into the host class, but we let the name come from the specific run-time situation we’re in. This also means that if we write new utility classes, our main class will work with them “out-of-the-box”, with 0 code maintenance.
This is exactly the principle which makes ReporterManager such an extendable platform, and it’s a principle worth knowing. It allows us to dramatically decrease the dependences between different parts of our code, and add new features and capabilities without jeopardizing our existing code.
Summery
In this article we have ran through some of the principles behind class composition. Class composition is a wide and deep subject, and this article has only scratched the tip of the iceberg. Having said that, the subjects and examples we’ve covered are more than sufficient grounds for you to continue to explore the many uses and faces of class composition on your own.
A good place to start exploring can be the ReporterManager project, where class composition is used extensively between the different components. Once the FrameworkManager project will be released, it too can be a valuable guide to many nitpicking class composition issues.


