|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIt seems that in programming, a new paradigm rarely replaces the old one, rather it builds upon its predecessor. These days, for example, there is a lot of talk about Aspect Based Programming (AOP). It is an exciting new concept that can better express certain intentions than Object Oriented Programming (OOP) and sometimes provide better reuse. However, it is a general consensus that it’s not poised to replace the OOP. A little back in time, since the advent of COM, VB programmers were (albeit, often unknowingly) on forefront of a new paradigm, “component based programming”. They learned how to assemble components and to treat them as binary black boxes with well-defined, self-describing interfaces. It proved to be an effective way to assemble GUI, by combining different “controls” and creating new ones, or to assemble and separate business logic inside components. It also promoted an encapsulation, since “inheritance breaks encapsulation”, and VB, while incorporating “interface” construct, had no implementation inheritance capabilities. On the other hand, VB programmers were often treated as pariah of programming community. On Java side, they frowned at the fact that VB had no inheritance, while C++ programmers had a difficult time getting over the fact that VB had no pointers and almost no direct memory manipulation. VB was often called a “toy language”. The tool and the language also carried a lot of burden from the past, like possibility to use global functions and data, sub-procedure control statements and syntax that was rarely to anyone’s liking, just to name a few. Even the border between tool and the language was rather blurred. While it is possible to write good code in VB, the tool’s RAD nature led to code where quality is rarely taken into consideration. It is easy to write code where lines proliferate, but structure and reuse are often lacking. For certain small projects, the visual development is remarkably effective. On the other hand, with VB entry into enterprise, especially with MTS components or COM+ and Microsoft’s distributed “DNA” architecture, and evolution from client-server, developing robust software with VB became increasingly difficult. Still, VB has won a role in the enterprise, and it is a popular tool for proprietary software development. These days, VB.NET has most of the characteristics of modern OO languages. It has been a major shift from the previous version and a new direction in sense of programming paradigm, in a certain way marking a return to basic OO principles. While components are still there, VB now boasts inheritance. A lot of enterprises are now in a situation where they will need to maintain legacy VB code-base along with one newly developed in VB.NET. It is easy to imagine why this fact implies additional cost for enterprise. Development teams need to maintain double set of skills. Interoperability and reuse, while possible, is difficult, and all benefits of new platform are not available to legacy programmers. The old tool will lack a manufacturer support. This can unquestionably be a turning point for an enterprise, since outdated information technology can haunt business and limit its capacity to respond quickly to new challenges. For example, thanks to the underlying platform, VB.NET is “web service ready” while productivity of VB6 in this area is much more limited. And while differences between VB.NET and prior VB versions are significant, there is still a good base for upgrade and a decent tool support for this purpose. In this article, we will take a look now at some of the common VB 6 idioms. We will use a popular VB 6 “Engine Collection Class” pattern. Article is available here and source code available for download at this link. We'll try to illustrate how we can migrate code while trying to reap the benefits of the new language capabilities. We will see that common refactoring transformations provide an excellent base for upgrade. Please note that we will not dwell on language and syntax details, since there are tools and guides that can lead through this process. Suffice to say, we need to get our VB 6 code working inside VB.NET IDE with bare minimum of changes applied. We advise making most of the changes with VB.NET because of stricter language and better testing framework support. We can make use of “Code Advisor”, a free tool from Microsoft that works as a plug-in for VB 6. It parses the code and makes “FixIt” comments in code, marking parts of code that will not be migrated automatically. It also permits adding custom code-checking rules. Another detail, conditional compilation will get into the way of upgrade. So, one trick is replacing conditional compilation variables with global (module) variables and conditional compilation keywords with ordinary conditions. Replace: #If DebugVersion Then
'...
#End If
with: If DebugVersion Then
'...
End If
And declare variable in Public DebugVersion As Boolean
After upgrade, we can erase global variables, and turn conditional compilation back on. Hopefully, we have some testing harness in place that can help us make these modifications safely. If not, we can get hold of some automated testing tool and generate it. This should help us open up our project with Visual Studio .NET IDE, when upgrade will take place, smoothly. Warming up the cauldronTransmutation No. 1: Write Unit TestsBefore we start making any changes to our code, we need to make sure we don’t break it instead. We can use open source NUnit framework to help us with this task. NUnit is a .NET port of a rather well known unit testing framework in Java – JUnit. Working with existing code can be very challenging for test-infected programmers, since adding tests to existing code opposed to writing code and testing simultaneously imposes a lot of difficulties. One approach is finding an “inflection point”, a narrow interface to a set of classes. Once inflection point is identified, we need to cover it with a new unit test. With time, more and more code will be under testing harness, giving us confidence when changing and modifying code. Transmutation No. 2: Replace Variant with Strong Types and deal with optional parameters
Upon upgrading our VB 6 project to VB.NET with Visual Studio .Net IDE, all We just got back a compiler working for us full-time; this will help us avoid nasty surprises in runtime. VB.NET does not understand Here is the code snippet: Private Const IS_MISSING_OPTIONAL_PARAM_INT As Integer = -1
'We could write similar functions for other types (for example String etc.)
Public Function IsMissing(ByVal Param As Integer) As Boolean
If Param = IS_MISSING_OPTIONAL_PARAM Then
IsMissing = True
Else
IsMissing = False
End If
End Function
'Sample function with optional parameter declaration
Private Sub Releasing(Optional ByVal Resource As _
Integer = IS_MISSING_OPTIONAL_PARAM)
If IsMissing(Resource) Then
'Parameter is missing
Else
'Parameter is NOT missing
End If
End Sub
Main ingredientsTransmutation No 3: Introduce InheritanceWhile it was possible to simulate inheritance in VB6 by combining interfaces and delegation, this approach is tedious and in practice rarely used. This gives us a lot of playground for types of transformations we have in mind. In VB.NET, we can use “Extract Superclass” refactoring to provide for better reuse and better design in existing code. We can see that in ECC example, all “Class” types (as in Engine-Collection-Class) share some common methods. We will start off by applying refactoring on it. For example, we will break a Code snippet: Before: Public Class CAccount
Private mblnDirty As Boolean
Private mblnIsNew As Boolean
Private mblnClassStorage As Boolean
Private mSecToken As String
Private mlngAccountID As Integer
Public Property Dirty() As Boolean'...
Friend Property ClassStorage() As Boolean'...
Public Property IsNew() As Boolean'...
Public Property DeleteFlag() As Boolean'...
Friend Property SecurityToken() As String'...
Public Property AccountID() As Integer'...
'Rest of business-related properties
'...
End Class
After: 'New extracted abstract (MustInherit) super-class ECCClass
Public MustInherit Class ECCClass
Private mblnDirty As Boolean
Private mblnIsNew As Boolean
Private mblnClassStorage As Boolean
Private mSecToken As String
Public Property Dirty() As Boolean'...
Friend Property ClassStorage() As Boolean'...
Public Property IsNew() As Boolean'...
Public Property DeleteFlag() As Boolean'...
Friend Property SecurityToken() As String'...
End Class
'CAccount now inherits abstract super-class
Public Class CAccount
Inherits ECCClass
Private mlngAccountID As Integer
Public Property AccountID() As Integer
'Rest of business-related properties
'...
End Class
Transmutation No 4: Extract ClassWe need to give a “Collection” a bit more of thought. Firs thing that we can notice is that it acts both as a container and as a persistence mechanism for the “Class”. These are clearly two separate responsibilities. We are better off having them apart. We can accomplish this by applying “Extract Class” transformation. We will move We will keep Now, this sounds a bit complicated, but after looking at the code sample, you’ll see that there’s not much to it. Code snippet: Before: Public Class ColAccount
Implements System.Collections.IEnumerable
Private mcol As Collection
Private mvarSecurityToken As String
Public Function Add(ByVal AccountNumber As String, _
ByVal StatusID As Short, _
ByVal UserNumber As Integer) As Integer
Public Sub Clear()
Public ReadOnly Property Count() As Integer
Public ReadOnly Property Item(ByVal Index As Integer) As CAccount
Friend Property SecurityToken() As String
Public Function Delete(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
Friend Function Load(ByVal SecurityToken As String, _
ByVal FilledStorage As Collection) As Boolean
Public Function MarkForDelete(Optional ByVal _
Index As Integer = IS_MISSING_INT) As Boolean
Function GetEnumerator() As System.Collections.IEnumerator Implements _
System.Collections.IEnumerable.GetEnumerator
GetEnumerator = mcol.GetEnumerator
End Function
Public Function Remove(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
Public Function Undelete(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
Public Function Update(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
End Class
After: Public Class ColAccount
Implements System.Collections.IEnumerable
Friend mcol As Collection 'Used to store the Classes
'reference to AccountPersistence instance we are to delegate 'methods to
Private mPersistence As AccountPersistence
'Constructor
Public Sub New()
mcol = New Collection()
'Creates new instance of AccountPersistence
mPersistence = New AccountPersistence(Me)
End Sub
Public Sub Clear()
Public ReadOnly Property Count() As Integer
' Returns an item from the Collection or returns Nothing.
Public ReadOnly Property Item(ByVal Index As Integer) As CAccount
' Fills the Collection. Returns True or False.
Friend Function Load(ByVal SecurityToken As String, _
ByVal FilledStorage As Collection) As Boolean
Function GetEnumerator() As System.Collections.IEnumerator Implements _
System.Collections.IEnumerable.GetEnumerator
GetEnumerator = mcol.GetEnumerator
End Function
Public Function Remove(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
Public Function Add(ByVal AccountNumber As String, _
ByVal StatusID As Short, _
ByVal UserNumber As Integer) As Integer
' delegates
Public Function Delete(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
Delete = mPersistence.Delete(Index)
End Function
Public Function MarkForDelete(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
Public Function Undelete(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
' delegates
Public Function Update(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean
Update = mPersistence.Update(Index)
End Function
' delegates
Friend Property SecurityToken() As String
Get
SecurityToken = mPersistence.SecurityToken
End Get
Set(ByVal Value As String)
mPersistence.SecurityToken = Value
End Set
End Property
End Class
'New extracted class
Public Class AccountPersistence
Private mcol As ColAccount
Private mvarSecurityToken As String
' Constructor receives a reference to underlying collection
Public Sub New(ByRef pcolAccount As ColAccount)
mcol = pcolAccount
End Sub
Public Function Delete(Optional ByVal Index As _
Integer = IS_MISSING_INT) As Boolean'...
Public Function Update(Optional ByVal _
Index As Integer = IS_MISSING_INT) As Boolean
'...
Friend Property SecurityToken() As String'...
End Class
No 5: Extract Method - replace optional parameter with overloaded methodsThanks to the possibility to overload methods in VB.NET, we can make some further adjustments to our Our client code will not break since we can still call Code snippet: Before: Public Function Delete(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
Dim ErrorNum As Integer
Dim oDALEng As IOBPDA.IOBPConnection
Dim oCAccount As CAccount
Dim vParameters(0, 0) As Object
Dim inx As Integer
Dim SPName As String
Dim LowerLimit As Integer
Dim UpperLimit As Integer
If mcol.Count = 0 Then 'Nothing to Update
Delete = True
End If
'If Index is supplied then delete only the supplied record
If Not IsMissing(Index) Then
If Index < 1 Or Index > mcol.Count Then
Err.Raise'...
Else
LowerLimit = Index
UpperLimit = Index
End If
Else
LowerLimit = 1
UpperLimit = mcol.Count
End If
If Not SafeCreateObject(oDALEng, OBP_DA_CONNECTION, ErrorNum) Then
Err.Raise'...
End If
For inx = UpperLimit To LowerLimit Step -1
oCAccount = mcol.Item(inx)
If Not oCAccount.IsNew Then
'Delete from DB If Not New
ReDim vParameters(PARMUBOUND, 1)
With oCAccount
'Fill Parameter Array
.ClassStorage = True
vParameters(PARMNAME, 0) = _
PARMNAMESP_ACCOUNTACCOUNTID 'Name
'... set rest of parameters
.ClassStorage = False
End With
If Not oDALEng.Execute(SecurityToken, _
SP_D_ACCOUNT, vParameters) Then
Err.Raise'...
End If
End If
oCAccount = Nothing
'Remove from Collection
mcol.Remove((inx))
Next inx
Delete = True
Erase vParameters
End Function
After: 'Receives the Index parameter
Public Function Delete(ByVal Index As Integer) As Boolean
Dim LowerLimit As Integer
Dim UpperLimit As Integer
If Index < 1 Or Index > mcol.Count Then
Err.Raise'...
Else
LowerLimit = Index
UpperLimit = Index
End If
Delete = Delete(LowerLimit, UpperLimit)
End Function
'Without parameter
Public Function Delete() As Boolean
Dim LowerLimit As Integer
Dim UpperLimit As Integer
LowerLimit = 1
UpperLimit = mcol.Count
Delete = Delete(LowerLimit, UpperLimit)
End Function
'Extracted Delete method with lower and upper limit parameters is private
Private Function Delete(ByRef LowerLimit As Integer, _
ByRef UpperLimit As Integer)
Dim ErrorNum As Integer
Dim oDALEng As IOBPDA.IOBPConnection
Dim oCAccount As CAccount
Dim vParameters(0, 0) As Object
Dim inx As Integer
Dim SPName As String
If mcol.Count = 0 Then 'Nothing to Update
Delete = True
End If
If Not SafeCreateObject(oDALEng, OBP_DA_CONNECTION, ErrorNum) Then
Err.Raise'...
End If
For inx = UpperLimit To LowerLimit Step -1
' ... code to execute delete stored procedure – set rest of parameters
Next inx
Delete = True
End Function
' Change in ColAccount
' delegates
Public Function Delete(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
If Not IsMissing(Index) Then
Delete = mPersistence.Delete(Index)
Else
Delete = mPersistence.Delete()
End If
End Function
Transmutation No 6: Replace Delegation with InheritanceIn VB.NET, a standard way to implement a collection class is to extend some of the classes already provided by the .NET framework. This works well for our Code snippet: Before: Public Class ColAccount
Implements System.Collections.IEnumerable
Friend mcol As Collection
Private mPersistence As AccountPersistence
Public Sub New()
mcol = New Collection()
mPersistence = New AccountPersistence(Me)
End Sub
Public Sub Clear()
'Create new Collection
mcol = New Collection()
End Sub
Public ReadOnly Property Count() As Integer
Get
If mcol Is Nothing Then
Count = 0
Else
Count = mcol.Count()
End If
End Get
End Property
' Returns an item from the Collection or returns Nothing.
Public ReadOnly Property Item(ByVal Index As Integer) As CAccount
Get
Dim mintCodeID As Short
If mcol Is Nothing Or mcol.Count() = 0 Then
Exit Property
End If
Item = mcol.Item(Index)
End Get
End Property
' Fills the Collection. Returns True or False.
Friend Function Load(ByVal SecurityToken As String, _
ByVal FilledStorage As Collection) As Boolean
Load = False
mPersistence.SecurityToken = SecurityToken
mcol = FilledStorage
Load = True
End Function
Public Function GetEnumerator() As System.Collections.IEnumerator
Implements System.Collections.IEnumerable.GetEnumerator
GetEnumerator = mcol.GetEnumerator
End Function
Public Function Remove(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
Remove = False
If IsMissing(Index) Then
Me.Clear()
Remove = True
End If
If (Index < 1 Or Index > Me.Count) Then
Err.Raise'...
End If
mcol.Remove((Index))
Remove = True
End Function
Public Function Undelete ' ...
Public Function MarkForDelete ' ...
Public Function Add ' ...
'... Functions Delete, Update,
'SecurityToken delegates to AccountPersistance
End Class
After: 'ColAccount now inherits CollectionBase
Public Class ColAccount
Inherits CollectionBase
'Reference to AccountPersistence instanca we are to delegate methods to
Private mPersistence As AccountPersistence
Public Sub New()
mPersistence = New AccountPersistence(Me)
End Sub
' Returns an item from the Collection or returns Nothing.
Public ReadOnly Property Item(ByVal Index As Integer) As CAccount
Get
If InnerList.Count() = 0 Then
Exit Property
End If
'maintain compatibility (Underlying ArrayList 0 based)
Item = InnerList.Item(Index - 1)
End Get
End Property
' Fills the Collection. Returns True or False.
Friend Function Load(ByVal SecurityToken As String, _
ByVal FilledStorage As ArrayList) As Boolean
Dim account As CAccount
Load = False
mPersistence.SecurityToken = SecurityToken
InnerList.Clear()
For Each account In FilledStorage
InnerList.Add(account)
Next
Load = True
End Function
' Removes an item in the Collection if Index Key
' passed in or else it calls
' Clear. Returns True or False.
Public Function Remove(Optional ByVal Index _
As Integer = IS_MISSING_INT) As Boolean
Remove = False
If IsMissing(Index) Then
Me.Clear()
Remove = True
End If
If (Index < 1 Or Index > Me.Count) Then
Err.Raise(ERR_ACCOUNTINDEXOUTOFRANGE, _
"ColAccount.Remove PROC", _
"Remove Failed: Index out of range.")
End If
InnerList.Remove((Index))
Remove = True
End Function
'... Functions: MarkForDelete, Undelete, Add, Delete, Update
End Class
Transmutation No. 7: Dealing with persistence class: Extract Super
From Similarly, from What we are actually doing in this point, is preparing “hook” methods; once we perform extract superclass, only these methods will need to be implemented. Now we can extract Super. We can call it Code snippet: Before: Public Class AccountPersistence
' ...
Private Function Delete(ByRef LowerLimit As Integer, _
ByRef UpperLimit As Integer)
Dim oDALEng As IOBPDA.IOBPConnection
'... declare rest of variables ...
For inx = UpperLimit To LowerLimit Step -1
oCAccount = mcol.Item(inx)
If Not oCAccount.IsNew Then
'Delete from DB If Not New
ReDim vParameters(PARMUBOUND, 1)
With oCAccount
'Fill Parameter Array
.ClassStorage = True
vParameters(PARMNAME, 0) = _
PARMNAMESP_ACCOUNTACCOUNTID 'Name
'... set rest of parameters
.ClassStorage = False
End With
If Not oDALEng.Execute(SecurityToken, _
SP_D_ACCOUNT, vParameters) Then
Err.Raise'...
End If
End If
oCAccount = Nothing
'Remove from Collection
mcol.Remove((inx))
Next inx
Delete = True
End Function
' ...
End Class
After: Public MustInherit Class ECCPersistence
Private mvarSecurityToken As String
Private mcol As ColAccount
Public Sub New(ByRef pcolAccount As ColAccount)
mcol = pcolAccount
End Sub
Protected MustOverride Function StoredProcInsert() As String
Protected MustOverride Sub UpdateCollectionWithReturnedData(ByVal _
rsAccount As Recordset, ByRef entity As ECCClass)
Protected MustOverride Function StoredProcUpdate() As String
Protected MustOverride Function PrepareUpdateData(ByVal eccentity _
As ECCClass, ByVal vParameters As Object) As Object
Protected MustOverride Function PrepareDeleteData(ByVal oEntity _
As ECCClass, ByVal vParameters As Object) As Object
Protected MustOverride Function StoredProcDelete() As String
Public Function Delete() As Boolean' ...
Public Function Delete(ByVal Index As Integer) As Boolean' ...
Private Function Delete(ByRef LowerLimit As Integer, _
ByRef UpperLimit As Integer) As Object
Dim oDALEng As IOBPDA.IOBPConnection
'... declare rest of variables
If mcol.Count = 0 Then 'Nothing to Update
Delete = True
End If
For inx = UpperLimit To LowerLimit Step -1
oEntity = mcol.Item(inx)
If Not oEntity.IsNew Then
vParameters = PrepareDeleteData(oEntity, vParameters)
If Not oDALEng.Execute(SecurityToken, _
StoredProcDelete, vParameters) Then
Err.Raise'...
End If
End If
'Remove from Collection
mcol.Remove((inx))
Next inx
Delete = True
End Function
Friend Property SecurityToken() As String' ...
Public Function Update(ByVal Index As Integer) As Boolean' ...
Public Function Update() As Boolean ' ...
Private Function Update(ByRef LowerLimit As Integer, _
ByRef UpperLimit As Integer)
Dim oDALEng As IOBPDA.IOBPConnection
'... declare rest of variables ...
For inx = LowerLimit To UpperLimit
oEntity = mcol.Item(inx)
If oEntity.Dirty Then
'Extracted method hook
vParameters = PrepareUpdateData(oEntity, vParameters)
'Check to see if updating existing record to database
If oEntity.IsNew = False Then
'Update the record - Extracted method hook
SPName = StoredProcUpdate()
Else
'Inserting the record - Extracted method hook
SPName = StoredProcInsert()
End If
If Not oDALEng.Execute(SecurityToken, _
SPName, vParameters, rsAccount) Then
Err.Raise'...
ElseIf (Not rsAccount Is Nothing) Then
If rsAccount.RecordCount > 1 Then
Err.Raise'...
Else
'Extracted method hook
UpdateCollectionWithReturnedData(rsAccount, oEntity)
End If
End If
End If 'Dirty
Next inx
Update = True
Erase vParameters
End Function
End Class
'The class implements all abstract methods – see GOF Template pattern
Public Class AccountPersistence
Inherits ECCPersistence
Public Sub New(ByRef pcolAccount As ColAccount)
MyBase.New(pcolAccount)
End Sub
Protected Overrides Function PrepareDeleteData(ByVal oCAccount _
As ECCClass, ByVal vParameters As Object) As Object
Dim account As CAccount
account = oCAccount
'Delete from DB If Not New
ReDim vParameters(PARMUBOUND, 1)
With account
.ClassStorage = True
vParameters(PARMNAME, 0) = PARMNAMESP_ACCOUNTACCOUNTID 'Name
'... set rest of parameters
End With
End Function
Protected Overrides Sub UpdateCollectionWithReturnedData(ByVal _
rsAccount As Recordset, ByRef entity As ECCClass)
Dim account As CAccount
account = entity
With account
'... set account with values from recordset
End With
End Sub
Protected Overrides Function StoredProcUpdate() As String
StoredProcUpdate = SP_U_ACCOUNT
End Function
Protected Overrides Function StoredProcInsert() As String
StoredProcInsert = SP_I_ACCOUNT
End Function
Protected Overrides Function StoredProcDelete() As String
StoredProcDelete = SP_D_ACCOUNT
End Function
Protected Overrides Function PrepareUpdateData(ByVal eccentity _
As ECCClass, ByVal vParameters As Object) As Object
' ... set update parameters ...
End Function
End Class
Transmutation No. 8: Dealing with collection class: Introduce Constructor and extract subclassLet’s take a look at Since Now we will extract new We can continue with identifying and defining new levels of abstraction. Since our After this, we need to go back to our If we take a look at the static structure diagram below, we can see that there is a layer of abstraction appearing, most of the classes now belonging to a certain hierarchy.
Diagram 1 Transmutation No. 9: AttributesVB.NET supports new metadata construct – attributes. In some cases, .NET framework services can be implemented as simply as marking certain element with appropriate attribute. Serialization is one of those services. All we need to do is mark certain classes with We will mark our Transmutation No 10: Introduce Factory PatternEngine class can be transformed along similar lines as a Following the usual lines, we will define an abstract ( In oColAccount = New ColAccount
into: oCollAccount = CreateCollectionInstance()
Method declaration looks like this: Public MustOverride CreateCollectionInstance() As ECCCollection
This way, we provide a “hook” for an implementing class to decide on what type of object to create. Needless to say, object in question needs to extend Our Code snippet: Before: Public Class <CODE>IOBPAccountEng
Public Function GetAccount(ByVal SecurityToken As String, _
Optional ByVal AccountID As Integer = IS_MISSING_INT, _
Optional ByVal DeletedRecords As IOBPAccountEng.DeleteRecordType _
= DeleteRecordType.NonDeletedRecords) As ColAccount
'... prepare stored procedure parameters and add them to vParameters array
If Not IsEmptyArray(vParameters) Then
If Not oDALEng.Execute(SecurityToken, SP_S_ACCOUNT, _
vParameters, rsAccount) Then
Err.Raise'...
End If
Else
If Not oDALEng.Execute(SecurityToken, SP_S_ACCOUNT, , rsAccount) Then
Err.Raise'...
End If
End If
oDALEng = Nothing
oColAccount = New ColAccount()
If (Not rsAccount Is Nothing) Then 'Set to True if nothing returned in DB
'Update collection with returned data
col = New ArrayList()
Do Until rsAccount.EOF
' loop over recordset and add instances to a collection
col.Add(oCAccount)
rsAccount.MoveNext()
Loop
End If
If Not oColAccount.Load(SecurityToken, col) Then
Err.Raise'...
End If
'Success
GetAccount = oColAccount
End Function
' Returns an empty Collection so that new Classes can be added.
Public Function NewAccount(ByVal SecurityToken As String) As ColAccount
Dim oColAccount As New ColAccount
oColAccount.SecurityToken = SecurityToken
NewAccount = oColAccount
End Function
Public Function Search('...
'...
End Class
After: Public MustInherit Class ECCEngine
Protected MustOverride Function CreateCollectionInstance() As ECCCollection
Protected MustOverride Sub SetClassData(ByRef entity As ECCClass, _
ByVal rsAccount As Recordset)
Protected MustOverride Function StoredProcSelect() As String
Protected MustOverride Function CreateClassInstance() As ECCClass
Public Function GetECCClass(ByVal SecurityToken As String, _
Optional ByVal ID As Integer = IS_MISSING_INT, _
Optional ByVal DeletedRecords As DeleteRecordType = _
DeleteRecordType.NonDeletedRecords) As ColAccount
'... prepare stored procedure parameters and add them to vParameters array
If Not IsEmptyArray(vParameters) Then
'Extracted method hook
If Not oDALEng.Execute(SecurityToken, _
StoredProcSelect, vParameters, rs) Then
Err.Raise'...
End If
Else
'Extracted method hook
If Not oDALEng.Execute(SecurityToken, StoredProcSelect, , rs) Then
Err.Raise'...
End If
End If
'Extracted method hook
oCol = CreateCollectionInstance()
If (Not rs Is Nothing) Then 'Set to True if nothing returned in DB
'Update collection with returned data
col = New ArrayList()
Do Until rs.EOF
' loop over recordset and add instances to a collection
col.Add(oClass)
rs.MoveNext()
Loop
End If
If Not oCol.Load(SecurityToken, col) Then
Err.Raise'...
End If
'Success
GetECCClass = oCol
Erase vParameters
End Function
' Returns an empty Collection so that new Classes can be added.
Public Function NewClass(ByVal SecurityToken As String) As ColAccount
Dim oECCCol As ECCCollection
'Extracted method hook
oECCCol = CreateCollectionInstance()
oECCCol.SecurityToken = SecurityToken
NewClass = oECCCol
End Function
End Class
Public Class IOBPAccountEng
Inherits ECCEngine
Public Function GetAccount(ByVal SecurityToken As String, _
Optional ByVal AccountID As Integer = IS_MISSING_INT, _
Optional ByVal DeletedRecords As DeleteRecordType = _
DeleteRecordType.NonDeletedRecords) As ColAccount
GetAccount = GetECCClass(SecurityToken, AccountID, DeletedRecords)
End Function
Protected Overrides Sub SetClassData(ByRef entity As ECCClass, _
ByVal rsAccount As Recordset)
Dim oAccount As CAccount
oAccount = entity
With oAccount
.AccountID = rsAccount.Fields(FN_ACCOUNTACCOUNTID).Value
'... set oAccount with rest of values from recordset
End With
End Sub
Public Function NewAccount(ByVal SecurityToken As String) As ColAccount
NewAccount = NewClass(SecurityToken)
End Function
Protected Overrides Function CreateCollectionInstance() As ECCCollection
Dim col As New ColAccount()
CreateCollectionInstance = col
End Function
Protected Overrides Function CreateClassInstance() As ECCClass
Dim entity As New CAccount()
CreateClassInstance = entity
End Function
Protected Overrides Function StoredProcSelect() As String
CreateCollectionInstance = col
StoredProcSelect = SP_S_ACCOUNT
End Function
Public Function Search('...
'...
End Class
Transmutation No 11: Extract InterfaceAt this point, our “mixture” is doing quite well. Still, if you have a refined sense of smell, there is still a faint odor coming out from our code stew. And, sinc | ||||||||||||||||||||