CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
One of the new buzzwords of the day is n-tier design. What does this mean and how does it affect your system development work? How many tiers are n? What are those tiers? In this chapter well examine the n-tier design model and understand the advantages it can provide. Well investigate the concept of business logic and see how it differs from data validation. The use of classes dedicated to providing business logic enforcement will be described and you will see how to incorporate these objects into your applications. Finally, well examine the use of ActiveX servers for the business logic objects and see how this can contribute to future evolution of your applications into new technologies.
The buzzwords of the past can help us to understand n-tier design. Client-server system design is based on the principle of dividing a data management system into a front end that provides the user interface and a back end that provides the data storage and retrieval. Client-server design could be called 2-Tier design because there are two tiersthe user interface tier and the database tier.
N-tier design simply indicates that there will be more than two tiers. For a while the buzzword was 3-tier design, but the '3' was replaced by 'N' to address designs that had more than three levels. In the 3-tier system design model, the tiers are user interface, business logic, and database. Additional levels considered in n-tier designs can be hardware interface, communications, operating system interface, and others. With a high-level language like Visual FoxPro, you arent often concerned with hardware and operating system interfaces because those responsibilities are handled for you.
In this chapter I will focus on the 3-tier model and especially on the middle tierthe business logic tier. Figure 1 shows the 3-tier design model.
Figure 1. The 3-tier system design model.
Figure 1 shows three blocks for the user interface. This means there can be more than one interface for getting at the data. These interfaces might be your applications forms, a spreadsheet application, and an ad hoc report generator. There could be more than three interfaces. The data storage and retrieval might constitute more than one database, although the diagram shows only one.
The business-logic block in Figure 1 is the focus of this chapter. It can represent a single object or many objects. I'll examine the business-logic layer in more depth for the rest of this chapter.
Business logic, which is sometimes referred to as business rules, comprises the set of rules that describes the storage and retrieval of data within the context of a specific business. Business logic cannot be defined in a vacuum; it requires heavy input from the experts within the business itself. You, as a developer, can implement business-logic enforcement, but your discipline cannot define the rules. You need the consultation of experts in the business, for whom the system is being built, in order to successfully define the business logic. These people are called business domain experts.
To understand the concept of business logic, lets examine a real-world example. Using the time and billing example database from the download files, you will examine an imaginary consulting firm. This firm has employees who work on projects for clients. In talking with the management of the company, you are told that employees are restricted to seeing data only regarding the projects that they are assigned to. This issue of restricting the display of data to only those projects for a particular employee is an example of a business logic rule. You would never know this from studying the database design. Even reviewing the validation rules for the data wouldnt reveal this simple but important rule.
In further discussions with management, you find out that the business considers a project to be an assignment of an employee to a client for a particular short-range or long-range task. Management tells you that the people in the office always want to see projects this way, with the client and the employee identified. Project information is meaningless to them unless they can see the client and employee information as well. This is another issue that can be enforced through the business logic rules.
You can see from the issues above that business logic encompasses a set of rules about how the business sees its data. Wouldnt some of these issues be dealt with in the data-validation rules related to the data-domain definitions? To see how these business-logic rules are different from data-domain definitions, lets look again at the two examples.
If the first example, where employees can see only their projects, were to be mapped to database design, it would likely cause you to design multiple tables for projectsone table for each employee. Although this might help to enforce of the business rule, it would certainly complicate the process of reassigning a project. It would also introduce a level of serious complexity in dealing with an employee leaving the company and being replaced by a new one. It makes much more sense to manage this issue dynamically at the time that data is displayed.
You could handle the second situation through your form design and put the burden on the user interface. Although this might seem like a good solution, it has its problems in long-term maintenance of the system. It also limits the tools that the users can leverage in managing their data. The form approach to managing data does not allow the data to be accessed from other applications like Excel or a VB application without duplicating the data-management code.
Businesses are dynamic, changing over time. Your design should allow for relatively easy adaptation to changes in the business. By moving the business logic out of the user interface and the database, you allow for adapting to changes in the business logic without requiring major overhauls of the entire system.
Another area where business logic is different from data
validation can be found in the very rules you apply to the field-domain
definitions. In the example database, the Clients table has a field named
cPostalCode, which is a character field with a width of 20 characters. In your
meetings with the management of the company, they explained that they only
service clients in three states:
If you put this constraint on the field in the database, you create an inconsistency in the data definitions. The field is defined as storing the postal code for the location of a client. Postal codes cover the entire world. Your validation rule would restrict the postal codes to only part of the world. Any future change in the companys business practices would require a redefinition of the fields domain and changes to the database itself.
It would be much better in the long run if you define the
fields domain based on the entity without regard to the business practices of
the company, and implement the business rule through the business-logic layer
of the system. This would mean implementing a field-validation rule that would
allow any postal code to be entered in the table, and using a business-logic
rule that restricted the postal codes to those three states. With this design,
if the company ever dealt with the international work regulations and wanted to
start marketing its services in
In the earlier sections of this chapter you learned about the nature of business logic. In this section you will begin the design of your business-logic class hierarchy.
Abstraction is the process of describing a complex problem in simple terms. The abstraction of business logic as a requirement in your systems starts with designing a top-level class for all business-logic classes. You need to list the general requirements for a business-logic class. Table 1 is a partial list of those requirements.
Table 1. The essential design requirements for your business-logic base class.
Requirement |
Description |
Present data |
The programmers interface of a business class must be capable of presenting data to the user-interface objects in a fashion that enforces the business-logic rules and eliminates the need for the user-interface layer to store any knowledge of those business-logic rules. |
Process data |
The business-logic class must present programmers interface components that allow for the processing of data to enforce the business-logic rules during data update, insert, and delete operations. |
Allow for specialization on subclasses |
The base business-logic class must allow for easy subclassing for specializing to implement specific business logic rule sets. |
Lets design a business-logic class for the project entity as it was described above. This business class will begin with an abstract class definition as the base business class. Youll use a form as the base class so you can take advantage of a private data session to isolate the business objects data from any other data that may be in use.
The purpose of the business class is to manage access to the data and enforce business-logic rules in the process. Therefore, youll find that using business-logic objects in your applications will preclude the use of other data-management approaches. You might successfully build an application that mixes business-logic objects with other data-management approaches, but it would be quite difficult to maintain. The maintenance problems would be related to the fact that there are multiple methods in use for managing access to the data. A much better design would be to decide which way to manage data and stick with that approach throughout the application.
Lets get on to describing the base business class. In the project named Chapter8, youll find a class library named Business. This class library holds two class definitions, busBase and busProject. Your base business class is busBase. This class is a subclass of the form with the properties and methods added to it. The properties and methods are listed in Table 2 and Table 3, respectively.
Table 2. The properties of your busBase class.
Custom |
Description |
CurrentView |
Contains a value that represents the current view of the business data. A business object may present data that can be seen in different 'views,' such as the Project business object, which can be seen as client projects or as employee projects. This property will track the current 'view' of the data in the business object. |
DataPath |
The path for finding the database and tables. |
Table 3. The custom methods of the busBase class.
Custom Method |
Description |
SetUpDataView |
Moves the business objects data to a requested 'record.' Not coded in the base class due to the fact that the code is totally dependent on the detailed nature of the business object. |
GetFields |
Returns a string of field names (including the cursor alias) delimited with commas for the business data. Not coded in the base class. |
GetFldData |
Returns the current value for a specific field. Not coded in the base class. |
GetNext |
Moves to the next or previous record in the business data. Not coded in the base class. |
OpenData |
Opens the data for the business object. Not coded in the base class because it is coded differently for each subclass. |
PutFldData |
Puts a value into a field in the business data. Accepts two parameters: the alias.fieldname and the value to be put into the field. Returns a numeric value of 1 (successful), -1 (bad field name), -2 (data type mismatch), -3 (business rule violated), and -4 (bad alias name). |
RevertData |
Issues TableRevert for each cursor open in the business object. |
SaveData |
Issues TableUpdate for each cursor in the business object. The updates are wrapped in a transaction so it will be an all-or-none proposition. The ValidateRecord method is called for each alias. Returns .T. if successful and .F. if unsuccessful. |
ValidateField |
Called for each PutFldData operation. Accepts two parameters the alias.fieldname and the value to be tested. Can return logical, .T. for OK and .F. for bad data value. Not coded in the base class. |
ValidateRecord |
Similar to ValidateField except it is a record-level validation. One parameter is passedthe alias name to be validated. Not coded in the base class. |
The three methods that contain code in the busBase class are PutFldData, RevertData, and SaveData. Listing 8-1 shows the code for PutFldData.
Listing 8-1. The code for PutFldData.
LPARAMETERS pcField, pxValue
* Puts data into a field
* pcField is the alias qualified field name
* pxValue is the expression to be put into the field
* Calls ValidateField to check the validity of the field's value
* Returns 1 for success
* -1 for invalid field name
* -2 for data type mismatch
* -3 for rule violation
* -4 Invalid alias in the field name
LOCAL lcAlias, lcUpdateAlias
IF TYPE(pcField) = 'U' OR NOT '.' $ pcField
* Invalid field name
RETURN -1
ENDIF
IF TYPE('pxValue') <> TYPE(pcField)
* Data type mismatch
RETURN -2
ENDIF
IF NOT THISFORM.ValidateField( pcField, pxValue )
* Invalid data
RETURN -3
ENDIF
* Save the currently select alias
lcAlias = ALIAS()
* get the alias portion of the field name
lcUpdateAlias = LEFT( pcField, AT('.',pcField) -1 )
* Check to see that the alias of the field is valid
IF NOT USED( lcUpdateAlias )
RETURN -4
ENDIF
* If we have a valid alias select it
SELECT ( lcUpdateAlias )
* Update the field in the buffer
REPLACE (pcField) WITH pxValue
* Reselect the alias we had at the start of this method
IF NOT EMPTY( lcAlias )
SELECT ( lcAlias )
ELSE
SELECT 0
ENDIF
* Return success
RETURN 1
This code shows the RevertData method.
* Tablereverts all alias'
lnHowMany = AUSED( laAlias )
IF lnHowmany = 0
RETURN
ENDIF
FOR lnCnt = 1 TO lnHowmany
IF CursorGetProp('BUFFERING',laAlais(lnCnt,1)) > 1
TableRevert( .T., laAlias( lnCnt, 1 ) )
ENDIF
ENDFOR
RETURN
Finally, the SaveData method code for the busBase class:
* Tableupdates the alias's in a transaction
* Returns .T. for success and .F. for failure and reversion
LOCAL laAlias(1,2), llRollback, lnHowmany, lnCnt
* Get the number of alias' open
lnHowMany = AUSED( laAlias )
IF lnHowmany = 0
* If there are no alias' open get out
RETURN
ENDIF
* Work the alias'
FOR lnCnt = 1 TO lnHowmany
* Call the ValidateRecord for this alias
IF NOT THISFORM.ValidateRecord( laAlias( lnCnt, 1 ) )
* Failed validation
* Revert the data
THISFORM.RevertData()
* Get out
RETURN .F.
ENDIF
ENDFOR
* Start a transaction
BEGIN TRANSACTION
* Work the alias'
FOR lnCnt = 1 TO lnHowmany
* TableUpdate this alias
IF NOT TableUpdate( 1, .F., laAlias( lnCnt, 1 ) )
* Failed update
* Set rollback variable
llRollBack = .T.
* End the loop
EXIT
ENDIF
ENDFOR
IF llRollBack
* If the rollback variable is set
ROLLBACK
* Revert the buffers
THISFORM.RevertData()
ELSE
* No rollback then commit the transaction
END TRANSACTION
ENDIF
* Return the result
RETURN NOT llRollBack
The code in these methods is simplified to demonstrate the ideas involved. Any one of the methods could be expanded to handle additional behavior. For example, the SaveData method could be expanded to detect the reason why a table update failed and recover from those situations where recovery is possible. Expanding these methods for additional functionality is left as an exercise for the reader. Here, the purpose is to understand the nature of business classes and how you can build them.
Building a subclass
To demonstrate how to customize a business class, try building a project business class that enforces the following rules: Projects are only seen as related to either clients or employees, and the project size limit in your application is $100,000. For this example youll use the Time and Billing data supplied on the books CD.
The methods and events that have code added to them in your busProject class are Init, OpenData, Load, SetUpDataView, GetFldData, GetNext, and ValidateField. The code below shows the Init event for your busProject class.
SET MULTILOCKS ON
The following code shows the OpenData method:
* Open the tables and set the
buffering mode for the project data
USE DataEmployees ORDER PrimaryKey ALIAS Employees IN 0
SELECT Employees
CursorSetProp('BUFFERING',5)
USE DataClients ORDER PrimaryKey ALIAS Clients IN 0
SELECT Clients
CursorSetProp('BUFFERING',5)
USE DataProjects ALIAS Projects IN 0
SELECT Projects
CursorSetProp('BUFFERING',5)
The Init and OpenData methods simply open the tables required and set their buffering mode to optimistic table buffering. Following is the code in the Load event for your class:
SET DELETED ON
SET TALK OFF
This code sets a few environment options for the class. Listing 8-2 shows the SetUpDataView code.
Listing 8-2 . The SetUpDataView method code.
LPARAMETERS pcView, piId
* pcView = 'CLIENTS' or 'EMPLOYEES'
* piId = The PK for the client or employee to get projects for
IF EMPTY(pcView)
* No view supplied
RETURN .F.
ENDIF
IF pcView = 'CLIENTS'
* We want the client view
IF THISFORM.CurrentView <> 'CLIENTS'
* If we are not already seeing the Client
* view of the data set it up
SELECT Employees
SET RELATION TO
SELECT Projects
SET ORDER TO ClientId
SET RELATION TO iEmployeeID INTO Employees
SELECT Clients
IF EMPTY( piId )
SET KEY TO iClientID
ELSE
SET KEY TO piId
ENDIF
SET RELATION TO iClientID INTO Projects
LOCATE
ENDIF
ELSE
* We want the employee view
IF THISFORM.CurrentView <> 'EMPLOYEES'
* If we are not already seeing the Employee
* view of the data set it up
SELECT Clients
SET RELATION TO
SELECT Projects
SET ORDER TO EmployeeId
SET RELATION TO iClientID INTO Clients
SELECT Employees
IF EMPTY( piID )
SET KEY TO iEmployeeID
ELSE
SET KEY TO piId
ENDIF
SET RELATION TO iEmployeeID INTO Projects
LOCATE
ENDIF
ENDIF
* Set the current view property
THISFORM.CurrentView = pcView
RETURN .T.
The SetUpDataView method simply sets up the data relations and cursor orders to provide the requested 'view' of the data. This code uses tables from the database directly. The code could just as easily use local views or even remote views for its data (see Chapter 5 for more on views). Also notice that the code above uses the SET KEY command on the master table to restrict the child table to only the records for a given key. You might think of using a SET FILTER, but SET KEY is much faster, even if the filter condition is fully optimizable.
Here is the GetFldData method for the busProject class:
LPARAMETERS pcField
IF EMPTY(pcField)
* No field supplied
RETURN .NULL.
ENDIF
RETURN EVALUATE(pcField)
The code listed above is very simple, returning the contents of a field passed as a parameter to the method. This code could be much more complex, depending on the business object you are creating. Perhaps youd need to make calculations or requery views before returning the field value.
Listing 8-3 is the code that moves the record pointer.
Listing 8-3. The GetNext method code
LPARAMETERS pcWhat, pnHowMany
* pcWhat = 'PROJECT' or empty indicating next master record
IF EMPTY( pcWhat )
* Force pcWhat to character
pcWhat = ''
ENDIF
IF EMPTY( pnHowMany )
* Default to 1
pnHowMany = 1
ENDIF
LOCAL lnRecno, llRet, lcAlias
IF EMPTY(THISFORM.CurrentView)
* If SetUpDataView has never been successful
RETURN llRet
ENDIF
* Save current alias
lcAlias = ALIAS()
IF THISFORM.CurrentView = 'CLIENTS'
* If we are in client view
IF UPPER(pcWhat) = 'PROJECT'
* If the request is for the next project
SELECT Projects
IF NOT EOF() AND NOT BOF()
* If we are not at eof or bof
lnRecno = RECNO()
* move in the direction requested
SKIP pnHowMany
IF EOF() OR BOF() OR iClientID <> Clients.iClientID
* If hit eof or bof or the project is not for the current client
GOTO lnRecno
ELSE
* We've got one
llRet = .T.
ENDIF
ENDIF
ELSE
* If the request was the next client
SELECT Clients
* Shut off the set key
SET KEY TO
IF NOT EOF() AND NOT BOF()
* If we are not at eof or bof
lnRecno = RECNO()
* move in the direction requested
SKIP pnHowMany
IF EOF() OR BOF()
* if we hit eof or bof
GOTO lnRecno
ELSE
* We've got one
llRet = .T.
ENDIF
ENDIF
* Reset the set key
SET KEY TO iClientID
ENDIF
ELSE
IF UPPER(pcWhat) = 'PROJECT'
SELECT Projects
IF NOT EOF() AND NOT BOF()
lnRecno = RECNO()
SKIP pnHowMany
IF EOF() OR BOF() OR iEmployeeID <> Employees.iEmployeeID
GOTO lnRecno
ELSE
llRet = .T.
ENDIF
ENDIF
ELSE
SELECT Employees
SET KEY TO
IF NOT EOF() AND NOT BOF()
lnRecno = RECNO()
SKIP pnHowMany
IF EOF() OR BOF()
GOTO lnRecno
ELSE
llRet = .T.
ENDIF
ENDIF
SET KEY TO iEmployeeID
ENDIF
ENDIF
* Reselect the alias
IF NOT EMPTY( lcAlias )
SELECT ( lcAlias )
ELSE
SELECT 0
ENDIF
* Return the result
RETURN llRet
The GetNext method allows the developer to request a movement in either the master or child cursor. The master is either the Clients or the Employees cursor, depending on the current view, and the child is the Projects cursor. The movement can be any number of records either forward or backward. This functionality is provided through the two accepted parameters: pcWhat and pnHowMany.
The first parameter, pcWhat, is either blank or 'PROJECTS.' If it's blank, the master alias for the view is used. If pcWhat is 'PROJECTS,' then the movement will be in the Projects alias. The second parameter, pnHowMany, is a number indicating the number of records to move. A positive number moves forward and a negative number moves backward.
The code for the client view movement is commented to describe each part of the operation. The employee view is essentially the same except it uses the employee alias instead of the client alias. The method returns either .T. or .F., indicating success or failure, respectively.
The following code shows the ValidateField method for the busProject class, and it implements the limitation on the billing estimate for the project to $100,000:
LPARAMETERS pcField, pxValue
* Validates field values according to the business logic rules
LOCAL llRet
llRet = .T.
DO CASE
CASE pcField = 'Projects.yProjectTotalBillingEstimate'
* Now allowed to exceed 100,000
IF pxValue > 100000 OR pxValue < 0
llRet = .F.
ENDIF
ENDCASE
RETURN llRet
Again, this code is greatly simplified from what you might see in a real business-class definition, but it does demonstrate the idea of validating on a field-by-field basis. In any real situation, the DO CASE construct would probably be much larger to handle the many fields requiring validation. If the validation code for any one field is complex enough, it might be implemented in a custom method of its own that is called from this method.
To demonstrate how you might put these business logic classes to use, I built a form named ClProj in the Chapter8 project. This form is based on your frmData class from Chapter 7 and uses the busProject business class as its data source. The data environment for ClProj is empty because the business class provides all data. In Chapter 7 you built a form class that managed the data. In that situation you used the forms data environment. Here you are building an n-tier system, one requirement of which is that the tiers you build be independent of each other. If you were to use and depend on the forms data environment in any way, youd preclude the design from being used when the user interface is not Visual FoxPro. So here you dont use the forms data environment.
There is code in a number of methods and events of the form. The code listed below shows the code in the forms Load event.
THISFORM.BusObj = NewObject('busProject','VCXBusiness.vcx')
THISFORM.BusObj.SetUpDataView('CLIENTS')
This code first creates an instance of the busProject class. It then calls the busProject objects SetUpDataView method, requesting the Client view of the data. Notice that you are storing the reference to the business object in a form property named BusObj. Using the BusObj property to hold the reference to the business object allows you to easily refer to the business object form with any code in any method of the form, or any objects in the form.
This line of code shows the Init of the form:
THISFORM.RefreshData()
The Init is calling a form method named RefreshData, which is a custom method that you have added to this form. The following code shows the code in the RefreshData method:
WITH THISFORM
.txtBase1.Value = .BusObj.GetFldData('Clients.cCompanyName')
.txtBase2.Value = .BusObj.GetFldData('Projects.cProjectName')
.txtBase3.Value = .BusObj.GetFldData('Employees.cLastName')
.txtBase4.Value = BusObj.GetFldData('Projects.yProjectTotalBillingEstimate')
ENDWITH
This method sets the value properties of various textboxes in the form to the value returned from calls to the business objects GetFldData method. The next methods you will examine are two of the frmData classs methods and two new ones you have added to this form. These are the GoNext, GoPrevious, GoNextProj, and GoPrevProj methods, respectively. Here is the GoNext method:
IF THISFORM.busObj.GetNext()
THISFORM.RefreshData()
ELSE
MessageBox( 'This is the last client record', MB_OK+MB_ICONINFORMATION, 'Last Record')
ENDIF
Here is the GoPrevious method:
IF THISFORM.busObj.GetNext('', -1)
THISFORM.RefreshData()
ELSE
MessageBox( 'This is the first client record', MB_OK+MB_ICONINFORMATION, 'First Record')
ENDIF
Notice the simplicity of the code in these two methods. They call the business objects GetNext method, passing the numeric argument for the proper movement. If GetNext fails, the forms code displays a message box to the user. An important thing to note here is that none of the code in the business class definitions ever displays any messages to the user.
This is because the business classes are data managers and therefore they have no responsibility to interact with the user. It is the responsibility of the form, which is the user interface object, to provide the interaction with the user.
Here is the GoNextProj method:
IF THISFORM.busObj.GetNext('PROJECT')
THISFORM.RefreshData()
ELSE
MessageBox( 'This is the last project record for this client', ;
MB_OK+MB_ICONINFORMATION, 'Last Record')
ENDIF
And here is the GoPrevProj method:
IF THISFORM.busObj.GetNext('PROJECT', -1)
THISFORM.RefreshData()
ELSE
MessageBox( 'This is the first project record for this client', ;
MB_OK+MB_ICONINFORMATION, 'First Record')
ENDIF
These last two methods are similar to the previous two, except for the first argument to the GetNext method and the text of the messages.
The click methods of the command buttons call the respective form methods listed above. Listing 8-4 shows the valid event for the textbox, which displays the estimated dollars.
Listing 8-4. The Valid for the textbox showing estimated dollars.
LOCAL lnReturn, llRet, lcMessage
IF THISFORM.ReleaseType > 0
* If the user is exiting the form discard the edit
RETURN
ENDIF
lcMessage = ''
lnReturn = ;
THISFORM.BusObj.PutFldData('Projects.yProjectTotalBillingEstimate', THIS.Value)
DO CASE
CASE lnReturn = 1
* Success
llRet = .T.
CASE lnReturn = -1
* Bad field name error
lcMessage = 'PROGRAM ERROR - Projects.yProjectTotalBillingEstimate ' + ;
'is not a valid field name'
CASE lnReturn = -2
* Data type mismatch
lcMessage = 'PROGRAM ERROR - Data type mismatch for field update'
CASE lnReturn = -3
* Rule violation
lcMessage = 'Estimated $ has an invalid value'
CASE lnReturn = -4
* Invalid alias
lcMessage = 'PROGRAM ERROR - The alias name for the field is invalid'
OTHERWISE
* Unknown error
lcMessage = 'PROGRAM ERROR - error nature is unknown'
ENDCASE
IF NOT llRet
Messagebox( lcMessage, MB_OK+MB_ICONEXCLAMATION, 'Data error')
THISFORM.BusObj.RevertData()
THISFORM.RefreshData()
ELSE
THISFORM.BusObj.SaveData()
ENDIF
RETURN IIF(llRet,llRet,0)
Figure 2 shows the form as its running.
Figure 2. The ClProjX form.
This chapter has discussed the design and programming of business-logic classes. The objects created from these classes provide data-management and rule-enforcement functionality. If Visual FoxPros database can enforce rules and the forms data environment can provide data access, why do you need these business-logic objects?
The accurate answer to the question is that you dont need them. Data access and rule enforcement can be accomplished without business-logic objects. However, using business-logic objects encapsulates the data access and rules enforcement in a layer that is separate from the database or the user interface. This separation provides the ability to modify an application to use a different interface application without affecting the database, and to change the database without affecting the interface.
In object-oriented design terminology, you have to provide a consistent interface between the database and the user interface through the business-logic object. You can change the interface or the database being used by simply making changes to the business-logic objects code.
Given this 'feature' of business-logic objects, there is a flaw in what youve done in these exercises so far. That flaw is that the user-interface layer of this design is restricted to Visual FoxPro. Although the business objects do make the coding of your forms consistent, they do not allow you to use a different application for the interface layer.
The next section will address this weakness and provide business classes that can be used even when Visual FoxPro is not the user-interface application.
Visual FoxPro can build ActiveX servers that contain your classes. These servers will expose your classes so they can be created using applications other than Visual FoxPro. A complete discussion of ActiveX servers is beyond the scope of this book, so this discussion will address only those issues that have a direct effect on your goal.
The first thing to do is create a new project named Chap8x to hold the classes for your server DLL. A copy of this project is in the download files. Your project contains the business class library. You need to edit each of the two classes and set them to be OLEPublic classes (using the Class Info dialog on the Project menu). Figure 3 shows this dialog for the busBase class.
Figure 3. The Class Info dialog for the busBase class.
Once the classes have been made OLEPublic, you have to build the DLL. In the project, click the Build button and select the Build OLE DLL option button. Figure 4 shows the build dialog set up in the proper way.
Figure 4. The ClProj form.
The process of building the DLL builds a DLL file and all of the required associated files, and then registers the DLL on the machine. If you distribute this DLL to other machines, youll need to register it on those machines before it can be used.
Lets use your server
In the project Chapter8 there is a second form named ClProjX. If you open this form for editing and look at the Load event, youll see the following code:
THISFORM.BusObj = CreateObject('Chap8X.busProject')
THISFORM.BusObj.SetUpDataView('CLIENTS')
The difference between this form and the ClProj form is the code used to create the busProject object. Here, the class used is chap8x.busproject, which refers to your ActiveX server. All of the remaining code in this form is exactly the same as in the ClProj form. The code doesnt need to be changed because the methods of the business object have the same names. The only difference is that one is a native VFP object and the other is an ActiveX server object.
Wheres the beef?
You now have your busProject class built as an ActiveX server. The major advantage of creating the business class in an ActiveX server is that you can now use this object in applications other than Visual FoxPro. To demonstrate this, try creating an Excel macro that uses your busProject class to create an object and then fills the cells in the Excel worksheet with data from your Visual FoxPro tables.
Listing 8-5 shows the Excel Visual Basic for Applications macro code.
Listing 8-5
Dim oProject As New chap8x.busproject
Sub project()
With ActiveSheet
.Cells(1, 1).Value = oProject.SETUPDATAVIEW('CLIENTS', '')
.Cells(1, 1).Value = 'Client'
.Cells(1, 2).Value = RTrim(oProject.GETFLDDATA('Clients.cCompanyName'))
.Cells(2, 1).Value = 'Employee'
.Cells(2, 2).Value = RTrim(oProject.GETFLDDATA('Employees.cLastName')) + ',
' +RTrim(oProject.GETFLDDATA('Employees.cFirstName'))
.Cells(3, 1).Value = 'Project'
.Cells(3, 2).Value = RTrim(oProject.GETFLDDATA('Projects.cProjectName'))
.Cells(4, 1).Value = 'Estimate'
.Cells(4, 2).Value = oProject.GETFLDDATA('Projects.
yProjectTotalBillingEstimate')
End With
End Sub
Note that the line getting the employees name should be one line of codenot two as formatted above. The two-line formatting was used here to fit the listing in the available space. The code in this macro first declares a new object from your busProject class in the chap8x.dll that you created earlier. Then a subprocedure is created that first sets up the view of the data and then calls the GetFldData method of your object to read data and write it to certain cells of the currently active Excel worksheet.
This Excel file is named Chapter8.xls in the download files. Figure 5 shows the Excel worksheet after you have chosen Tools Macros, highlighted your macro, and clicked Run.
Figure 5. The Excel worksheet after using
your busProject class to get data.
In Chapter 7, you built data-management classes for use exclusively within Visual FoxPro. These classes represented one way to approach accessing data. In this chapter you were introduced to a new way to handle data by using business-logic objects. These objects added some new dimensions to the data-management process. Among the new features was enforcement of logical rules, in addition to the database-integrity rules and presenting data in accordance with the natural way that a business sees its information.
The business classes you built were, admittedly, only demonstrations of the ideas. They were not complete business-logic classes; however, they did show you some ways of accomplishing the goals involved.
With these classes, you saw a mechanism of separating the user interface from the business logic and the business logic from the data. Using a form, you could easily change the data source to Microsoft SQL Server or Oracle by making changes only to the business logic classesand not to the form itself.
Then you took it all one step further by producing your business classes as an ActiveX server. This allowed you to use the same business logic in both Visual FoxPro and Excel. Microsoft Visual Basic, Microsoft Access, Borland Delphi, or any other application or development language that supports ActiveX servers could use this same busProject ActiveX server.
If you think about the last two chapters for a while, youll begin to notice that there are advantages and disadvantages to either approach. The approach in Chapter 7 suffers from being limited to Visual FoxPro, only because there is a dependence on FoxPros data environment and its data-binding capabilities. However, the Chapter 7 approach has the advantage of letting you work with the visual designers within Visual FoxPro, and that can reduce your development time considerably.
The Chapter 8 approach has the advantage of providing a degree of independence between the user interface and the data storage. The N-Tier design model allows you to use the same data with different front ends. One disadvantage of the N-Tier model is that the use of the Visual FoxPro developers interface is somewhat restricted when it comes to dealing with the data.
So, what do you do? Which approach should you use? The answers to those questions lie in a pragmatic review of the requirements of the application you are building. You should know how to build both models and choose the one that is best suited to the application at hand. It doesnt make sense to build an N-Tier model, with all of its inherent flexibility, if the application will never use any other interface than Visual FoxPro forms and never access data stored in any other database. Its equally nonsensical to build a model that only supports Visual FoxPro tiers, if that information will need to be accessed by Microsoft Excel or some other application.
The conclusion: Build the model that solves the problem at hand.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1025
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved