8.3 Creating behavioral objects

Creating behavioral objects

Behavioral objects are the first and easiest step toward larger, more advanced object systems. Behavioral objects are the workhorses of the application. They have no visible appearance. Other objects that call the behavioral object handle all user interface issues. Behavioral objects can handle entire logical areas (such as credit card transactions) or just very specialized tasks. This depends on the architecture you choose there is no right or wrong.

Behavioral objects are the object counterpart of functions and function libraries in the procedural world. As with all objects, they have attached data (variables or properties) that greatly enhances the number of options and possibilities you have.

Behavioral objects appear in a variety of incarnations. They can be manager objects that handle data access, forms, menus or other technical parts of the application. They can also be business logic objects that enforce business rules. Middle tiers are built entirely of behavioral objects (see Chapter 9). This means that a great part of every application is made of behavioral objects (typically one-third or more).

If you wanted to follow the COM Automation idea, you'd typically create behavioral objects and compile them into COM components that could be instantiated from every COM client. This is an easy way to reuse objects, although it isn't the most powerful (see below for more information about reusable objects).

A typical behavioral object is a data manager that handles all data retrieval or modification. I use such an object in all of my applications. In fact, I have an individual data manager for each one of my tables and views. Of course I don't start from scratch every time, but I have an abstract data manager class. In typical scenarios, I subclass this class and change a couple of properties to specify the table name (in plain-vanilla FoxPro tables), primary key fields and a couple of other options. The data manager objects have a couple of key methods. Because I subclass all manager objects from one abstract manager class, the interface is the same for all data manager classes. Figure 1 gives a brief overview of my data manager architecture.

Figure 1. A simple data manager architecture.

This figure shows a generic data manager class with a number of properties and methods. The class also has three subclasses (see Chapter 12 for a more detailed explanation of the notation). Of course, all those classes inherit all methods and properties.

In a regular scenario I don't have to change more than a couple of properties, but if I need a more customized version for one of my tables, I can always overwrite or extend the existing methods. I can even add new ones, but I try to stay away from this kind of change unless the new method is used only internally. New public methods would redefine the interface and therefore defeat all attempts to create a clean interface that can be used in a polymorphic environment.

The functionality of the data manager object shouldn't be addressed in this chapter because it doesn't have anything to do with behavioral objects. Behavioral objects are just an implementation detail of this particular class. However, I still want to mention a couple of things so you can understand the power behind this architecture. The properties you see in Figure 1 (which represent only a fraction of my data manager's properties) define what table I want to use, what the primary key field is and what I want to use as the default sort order. The object's featured methods are there to access data. The ExecuteQuery() method runs a regular SQL select statement. If I don't pass any parameters, I get a cursor that contains all the fields and records of the referenced table. Here's an example of how this method can be used:

oCustomerData.Query()

oInvoiceData.Query()

oCustomerData.Query("* WHERE NAME='A'")

As you can see, it doesn't matter what object I talk to. The query method behaves the same way no matter whether I query customer or invoice data. In fact, all I have to do is instantiate the objects and use them because all properties have already been set in the class.

The other methods of the data manager object work differently. Instead of dealing with cursors, they use data objects that are instantiated by the manager object. This kind of architecture is somewhat different from what most people would expect in Visual FoxPro, but then again, this particular implementation detail isn't the topic of this chapter. For now, all you need to know is that GetDataObject() returns an object that has no methods and one property for each field in the data source. The property's values are the same as the values in each field. The method requires the value of the primary key field of the desired record to be passed as a parameter like so:

loData = oCustomerData.GetDataObject("XHUYKIF1")

Other methods help me deal with the primary key value.

Once I have the data object I can hand it over to other objects, I can modify the field (property) values through various interfaces, and I can finally save the changes using the SaveDataObject() method like so:

oCustomerData.SaveDataObject( loData )

Again, notice that I don't need any knowledge specific to the current data source. This is important, because it means I can create generic classes that use this object. I need only one data entry form class, for instance. I will need to create subclasses of this class, but the modifications in each class will be minor. To save the modified data, I simply use a data object that's a member of the form and hand it over to the data manager object, no matter what the data source is or what particular interface class is currently in use.

As mentioned above, I use this approach to handle local data, but as we all know, in today's world we need to deal with other data sources such as SQL Server or ADO. Fortunately, the current architecture works great with these kinds of data sources. Figure 2 shows how I extended my class hierarchy to handle these scenarios.

As you can see, I added another layer of abstract manager classes one for every data source I want to handle. Each of those classes has a subclass for every table I'm using. Most of the work will be done in the second layer, which defines the individual data managers for the various data sources. The topmost class in the hierarchy (aDataManager) is used only to define the interface. It has all the properties and methods but no code attached to it. In the second layer, there are very few new properties, methods or objects. (Exceptions might be some methods on the SQL Server object that handles connections, but then again, those methods are used only internally so they don't clutter the interface.)

Figure 2. The revised data manager architecture that handles different data sources.

The second layer contains all the code. The code used in this layer is essentially different for all classes because they handle very different data sources. However, the interface is the same in all the classes, and the objects and cursors they create are identical. This is where the power of this architecture comes from. I can exchange the data manager objects, and everything else will remain the same. In fact, the rest of the application won't even notice that a different back end is utilized. As you can imagine, this kind of design is extremely easy to extend and to modify as long as the object interface remains the same.

This is only one example of a behavioral object. The idea behind it is rather simple, which is how it is with most behavioral objects. They help you to break large and complex behavior into small, well-defined and easy-to-manage pieces. Let's have a look at how those pieces come together.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net