Visual Basic

Creating a worker: Identification

In this design pattern, an ID is required for all worker objects. This ID, or string, is used to index the worker object into the factory collection. This ID could be manually determined by each individual factory, but I like having the ID as a property on the object-it makes automating identification of individual worker objects inside each factory a lot easier.

In most cases, the ID is the corresponding database ID, but what about when a worker object is created based on a table with a multiple field primary key? In this case, the ID would return a concatenated string of these fields, even though they would exist as explicit properties in their own right.

Displaying the data!

Here is the internal worker code for the ID property-the property responsible for setting and returning the worker object ID. Note that this code is identical for every other Property Let/Get pair in the worker object.

Public Property Get ID() As Long
      ID = oPicRecordset("ID")
  End Property
  Public Property Let ID(i_ID As Long)
      PiSetPropertyValue "ID", i_ID
  End Property

The most important point here is the PiSetAbsoluteRowPosition call. This call is required to point the worker object to the correct row in the shared recordset. The recordset current record at this point is undefined-it could be anywhere. The call to PiSetAbsoluteRowPosition is required to make sure that the worker object is retrieving the correct row from the recordset.

Private Sub PiSetAbsoluteRowPosition()
      oPiRecordset.AbsolutePosition = lPiRowIndex
  End Sub

Likewise, this call to PiSetAbsoluteRowPosition needs to happen in the Property Let. The PiSetPropertyValue procedure merely edits the appropriate row in the recordset.

Private Sub PiSetPropertyValue(i_sFieldName As String, _
                                 i_vFieldValue As Variant)
      oPiRecordset(i_sFieldName) = i_vFieldValue
  End Sub

Methods in the madness

At the moment, all we've concentrated on are the properties of worker objects. What about methods?

An Employee worker object might have methods such as AssignNewProject. How do you implement these methods? Well, there are no special requirements here-implement the customer business methods as you see fit. Just remember that the data is in the shared recordset and that you should call PiSetAbsoluteRowPosition before you reference any internal data.

Worker objects creating factories

Factory objects returning workers is all well and good, but what happens when we want to create relationships between our business objects? For example, an Employee object might be related to the Roles object, which is the current assignment this employee has. In this case, the Employee worker object will return a reference to the Roles factory object. The Employee object will be responsible for creating the factory object and will supply any parameters required for its instantiation. This is great because it means we need only to supply parameters to the first factory object we create. Subsequent instantiations are managed by the worker objects themselves.

Dim ocDAL            As New cDAL
  Dim ocfEmployees     As New cfEmployees
  Dim ocwEmployee      As New cwEmployee
  Dim ocParams         As New cParams
  ocfEmployees.Create ocDAL
  ocParams.Add "ID", "637"
  ocfEmployees.Populate ocParams
  MsgBox ocfEmployees(1).Roles.Count

Here the Roles property on the Employee worker object returns the ocfRoles factory object.

Public Property Get Roles() As cfRoles
      Dim ocfRoles As New cfRoles
      Dim ocParams As New cParams
      ocParams.Add "EmpID", Me.ID
      ocfRoles.Create oPicDAL
      ocfRoles.Populate ocParams
      Set Roles = ocfRoles
  End Property

Accessing child factory objects this way is termed navigated instantiation, and you should bear in mind this important performance consideration. If I wanted to loop through each Employee and display the individual Roles for each Employee, one data access would retrieve all the employees via the DAL and another data access would retrieve each set of Roles per employee. If I had 1800 employees, there would be 1801 data access operations-one operation for the Employees and 1800 operations to obtain the Roles for each employee. This performance would be suboptimal.

In this case, it would be better to perform direct instantiation, which means you'd create the Roles for all employees in one call and then manually match the Roles to the appropriate employee. The Roles object would return the EmployeeID, which we would then use to key into the Employee factory object to obtain information about the Employee for this particular Roles object. The golden rule here is that navigated instantiation works well when the number of data access operations will be minimal; if you need performance, direct instantiation is the preferred method.

Dim ocfRoles         As New cfRoles
  Dim ocfEmployees     As New cfEmployees
  Dim ocwRole          As cwRole
  ocfEmployees.Create oPicDAL
  ocfRoles.Create oPicDAL
  ocfEmployees.Populate     ' Using default populate to retrieve all objects
  For Each ocwRole In ocfRoles
      MsgBox ocfEmployees(ocwRole.EmpID).Name
  Next ' ocwRole

An interesting scenario occurs when a worker object has two different properties that return the same type of factory object. For example, a worker could have a CurrentRoles property and a PreviousRoles property. The difference is that these properties supply different parameters to the underlying factory object Populate procedure.

Query a worker object to determine what factory objects it supports as children

It's useful to be able to query a worker object to determine what factory objects it supports as children. Therefore, a worker object contains the read-only property Factories, which enables the code that dynamically determines the child factory objects of a worker and can automatically instantiate them. This is useful for utilities that manipulate business objects.

The Factories property returns a cParams object containing the names of the properties that return factories and the names of those factory objects that they return. Visual Basic can then use the CallByName function to directly instantiate the factories of children objects, if required.

The Factories property is hidden on the interface of worker objects because it does not form part of the business interface; rather, it's normally used by utility programs to aid in dynamically navigating the object model.

Dim ocfEmployees     As New cfEmployees
  Dim ocfEmployee      As New cfEmployee
  Dim ocParams         As New cParams
  ocfEmployees.Create oPicDAL
  Set ocfEmployee = ocfEmployees(1)
  Set ocParams = ocfEmployee.Factories