Visual Basic

Factory Objects

The interface for the factory object is as follows:

cFactory Interface

Member Description
Create Initializes the business object with a DAL object
Populate Creates worker objects according to any passed-in parameter object
Item Returns a worker object
Count Returns the number of worker objects contained in the factory
Add Adds an existing worker object to the factory
AddNew Returns a new empty worker object
Persist Updates all changes in the worker objects to the database
Remove Removes a worker object from the factory
Recordset Returns the internal factory recordset
Delete Deletes a worker object from the data source and the factory
Parameters Returns the parameters that can be used to create worker objects

Creating a factory object

Creating the factory is simple. The code below demonstrates the use of the Create method to instantiate a factory object. This code exists in the action objects (which we'll cover later). This sample action object uses the Employees business object to perform a business process.

Dim ocDAL As New cDAL
  Dim ocfEmployees As New cfEmployees
  ocfEmployees.Create ocDAL

Hold it-what's the DAL doing here? Isn't the purpose of business objects to remove the dependency on the DAL? Yes, it is. But something important is happening here, and it has everything to do with transactions. The scope and lifetime of the action object determines the scope and lifetime of our transaction for the business objects as well.

Say our action object needs to access four different factory objects and change data in each of them. Somehow each business object needs to be contained in the same transaction. This is achieved by having the action object instantiate the DAL, activating the transaction and passing that DAL to all four factory objects it creates. This way, all our worker object activities inside the action object can be safely contained in a transaction, if required. More on action objects and transactions later.

So what does the Create code look like? Too easy:

Public Sub Create(i_ocDAL As cDAL)
      Set oPicDAL = i_ocDAL ' Set a module-level reference to the DAL.
  End Sub

Populating a factory object

Creating a factory object isn't really that exciting-the fun part is populating this factory with worker objects, or at least looking like we're doing so!

Dim ocDAL           As New cDAL
  Dim ofcEmployees    As New cfEmployees
  Dim ocParams        As New cParams
  ofcEmployees.Create oDAL
  ocParams.Add "Department", "Engineering"
  ofcEmployees.Populate ocParams

Here a recordset has been defined in the DAL as Employees, which can take the parameter Department to retrieve all employees for a particular department. Good old cParams is used to send parameters to the factory object, just like it does with the DAL. What a chipper little class it is!

So there you have it-the factory object now contains all the worker objects ready for us to use. But how does this Populate method work?

Private oPicRecordset As cRecordset
  Public Sub Populate (Optional i_ocParams as cParams)
      Set oPicRecordset = oPicDAL.OpenRecordset("Employees", i_ocParams)
  End Sub

The important point here is that the Populate method is only retrieving the recordset-it is not creating the worker objects. Creating the worker objects is left for when the user accesses the worker objects via either the Item or Count method.

Some readers might argue that using cParams instead of explicit parameters detracts from the design. The downside of using cParams is that the parameters cannot be determined for this class at design time and do not contribute to the self-documenting properties of components. In a way I agree, but using explicit parameters also has its limitations.

The reason I tend to use cParams rather than explicit parameters in the factory object Populate method is that the interface to the factory class is inherently stable. With cParams all factory objects have the same interface, so if parameters for the underlying data source change (as we all know they do in the real world) the public interface of our components will not be affected, thereby limiting the dreaded Visual Basic nightmare of incompatible components.

Also of interest in the Populate method is that the cParams object is optional. A Populate method that happens without a set of cParams is determined to be the default Populate method and in most cases will retrieve all appropriate objects for that factory. This functionality is implemented in the DAL.

Obtaining a worker object

After we have populated the factory object, we can retrieve worker objects via the Item method as shown here:

Dim ocDAL            As New cDAL
  Dim ofcEmployees     As New cfEmployees
  Dim owcEmployee      As New cwEmployee
  Dim ocParams         As New cParams
  ofcEmployees.Create ocDAL
  ocParams.Add "Department", "Engineering"
  ofcEmployees.Populate ocParams
  Set owcEmployee = ofcEmployees.Item("EdmundsM")
  MsgBox owcEmployee.Department

At this point, the Item method will initiate the instantiation of worker objects. (Nothing like a bit of instantiation initiation.)

Public Property Get Item(i_vKey As Variant) As cwEmployee
      If colPicwEmployee Is Nothing Then PiCreateWorkerObjects
      Set Item = colPicwEmployee(i_vKey).Item
  End Property

So what does PiCreateWorkerObjects do?

Private Sub PiCreateWorkerObjects
      Dim owcEmployee As cwEmployee
      Do Until oPicRecordset.EOF
          Set owcEmployee = New cwEmployee
          owcEmployee.Create oPicRecordset, oPicDAL
          oPicRecordset.MoveNext
      Loop
  End Sub

Here we can see the payback in performance for using the Shared Recordset Model. Initializing the worker object simply involves calling the Create method of the worker and passing in a reference to oPicRecordset and oPicDAL. The receiving worker object will store the current row reference and use this to retrieve its data.

But why is the DAL reference there? The DAL reference is needed so that a worker object has the ability to create a factory of its own. This is the way object model hierarchies are built up. (More on this later.)

The Item method is also the default method of the class, enabling us to use the coding-friendly syntax of

ocfEmployees("637").Name

Counting the worker objects

Couldn't be simpler:

MsgBox CStr(ofcEmployees.Count)
  Private Property Get Count() As Long
      Count = colPicfEmployees.Count
  End Property

Says it all, really.

Adding workers to factories

Often you will have a factory object to which you would like to add pre-existing worker objects. You can achieve this by using the Add method. Sounds simple, but there are some subtle implications when using the Shared Recordset implementation. Here it is in action:

Dim ocDAL            As New cDAL
  Dim ocfEmployees     As New cfEmployees
  Dim ocwEmployee      As New cwEmployee
  ocfEmployees.Create ocDAL
  Set ocwEmployee = MagicEmployeeCreationFunction()
  ocfEmployees.Add ocwEmployee

You'll run into a few interesting quirks when adding another object. First, since the worker object we're adding to our factory has its data stored in another factory somewhere, we need to create a new row in our factory's recordset and copy the data from the worker object into the new row. Then we need to set this new object's recordset reference from its old parent factory to the new parent factory, otherwise it would be living in one factory but referencing data in another-that would be very bad. To set the new reference, we must call the worker Create method to "bed" it into its new home.

Public Sub Add(i_ocwEmployee As cwEmployee)
      oPicRecordset.AddNew
      With oPicRecordset.Fields
          .("ID") = i_ocwEmployee.ID
          .("Department") = i_ocwEmployee.Department
          .("FirstName") = i_ocwEmployee.FirstName
          .("LastName") = i_ocwEmployee.LastName
      End With
      oPicRecordset.Update
      i ocwEmployee.Create oPicRecordset
  End Sub

And there you have it-one worker object in a new factory.