Visual Basic

Factory-Worker Objects

So what, really, is a business object and how is it implemented? Well, in the Visual Basic world, a business object is a public object that exposes business-specific attributes. The approach TMS takes toward business objects is to employ the action-factory-worker model. We'll come to the action objects later, but for now we'll concentrate on the factory-worker objects.

A quick note about terminology: in this design pattern there is no such thing as an atomic "business object" itself. The combination of the interaction between action, worker, and factory can be described as a logical business object.

Factory-Worker Model

The factory-worker model stipulates that for each particular type of business object an associated management class will exist. The purpose of the management class is to control the creation and population of data in the business objects. This management class is referred to as a factory class. The interface of every factory class is identical (except under exceptional circumstances).

Likewise, the worker class is the actual business object. Business objects cannot be instantiated without an associated factory class. In Visual Basic-speak we say that they are "Public Not Creatable"-the only way to gain access to a worker object is through the publicly instantiable/creatable factory class. So when we refer to a business object we are actually talking about the combination of the factory and worker objects, since each is dependent on the other.

Shared recordset implementation

Adding all this factory-worker code to your application isn't going to make it any faster. If your worker objects had 30 properties each and you wanted to create 1000 worker objects, the factory class would have to receive 1000 rows from the database and then populate each worker with the 30 fields. This would require 30,000 data operations! Needless to say, a substantial overhead.

What you need is a method of populating the worker objects in a high-performance fashion. The Shared Recordset Model solves this problem, which means that one cRecordset is retrieved from the DAL and each worker object is given a reference to a particular row in that recordset. This way, when a property is accessed on the worker object, the object retrieves the data from the recordset rather than from explicit internal variables, saving us the overhead of populating each worker object with its own data.

Populating each worker object involves instantiating only the object and then passing an object reference to the Shared Recordset and a row identifier, rather than setting all properties in the worker object individually. The worker object uses this object reference to the recordset to retrieve or write data from its particular row when a property of the business object is accessed. But to establish the real benefits of using the factory-worker approach, we need to discuss briefly how distributed clients interface with our business objects. This is covered in much greater detail in the sections, "Action Objects" and "Clients," later in this chapter.

To make a long story short, distributed clients send and receive recordsets only. Distributed clients have no direct interface to the business objects themselves. This is the role of action objects. Action objects act as the brokers between client applications and business objects. The recordset supports serialization, so the clients use this "serializable" recordset as a means of transferring data to and from the client tier to the business tier via the action object.

It's quite common for a client to request information that originates from a single business object. Say, for example, that the client requests all the information about an organization's employees. What the client application wants to receive is a recordset containing all the Employee Detail information from an action object. The EmployeeDetail recordset contains 1800 employees with 30 fields of data for each row.

Let's look at what's involved in transferring this information from the business objects to the client if we don't use the Shared Recordset implementation.

  1. The client requests the EmployeeDetail recordset from the Employee Maintenance action object.
  2. The Employee Maintenance action object creates an Employee factory object.
  3. The Employee factory object obtains an Employee recordset from the DAL.
  4. The Employee factory object creates an Employee worker object for each of the rows in the recordset.
  5. The Employee factory object sets the corresponding property on the Employee worker object for each of the fields in that row of the recordset.

    We now have a factory-worker object containing information for our 1800 employees. But the client needs all this information in a recordset, so the client follows these steps:

  6. The Employee Maintenance action object creates a recordset.
  7. The Employee Maintenance action object retrieves each Employee worker object from the Employee factory object and creates a row in the recordset.
  8. Each property on that Employee worker object is copied into a field in the recordset.
  9. This recordset is returned to the client and serialized on the client side.
  10. The client releases the reference to the action object.

Basically, the business object gets a recordset, tears it apart, and then the action object re-creates exactly the same recordset we had in the first place. In this case, we had 1800 30 data items that were set and then retrieved, for a total of 108,000 data operations performed on the recordsets!

Let's look at the difference if we use the Shared Recordset Model.

  1. The client requests an EmployeeDetail recordset from the Employee Maintenance action object.
  2. The Employee Maintenance action object creates an Employee factory object.
  3. The Employee factory object obtains an Employee recordset from the DAL.
  4. The Employee factory object keeps a reference to the recordset.

    NOTE


    Notice that at this point the factory will not create any objects; rather, it will create the objects only the first time they are requested-in essence, it will create a Just In Time (JIT) object.
  5. The Employee Maintenance action object obtains a reference to the recordset from the Employee factory object via the Recordset property.
  6. This recordset is returned to the client and serialized on the client side.
  7. The client releases the reference to the action object.

Total data operations on the recordset-zero. We can now return large sets of data from a business object in a high-performance fashion. But this leads to the question of why you should bother with the business objects at all. If all the client is doing is requesting a recordset from the DAL, why the heck doesn't it just use the DAL directly?

Well, to do so would completely ignore the compelling arguments for object-oriented programming. We want contained, self-documenting abstracted objects to represent our core business, and this scenario is only receiving data, not performing any methods on these objects.

Remember that this is a particular case when a client, on a separate physical tier, requests a set of data that directly correlates to a single business object. The data will often need to be aggregated from one or more business objects. So the action object, which exists on the same physical tier as the business object, will have full access to the direct worker object properties to perform this data packaging role.

A client will often request that a complex set of business logic be performed. The action object will perform this logic by dealing directly with the worker objects and the explicit interfaces they provide. Thus, the action objects can fully exploit the power of using the objects directly.

Using the worker objects directly on the business tier means we are creating much more readable, self-documenting code. But because of the advantages of the Shared Recordset implementation, we are not creating a problem in terms of performance. If we need to shift massive amounts of data to the client, we can still do it and maintain a true business object approach at the same time-we have the best of both worlds.

Now that we have covered the general architecture of the action-factory-worker-recordset interaction, we can take a closer look at the code inside the factory and worker objects that makes all this interaction possible.