Let's take a simple Visual Basic application, like any we may have created in Visual Basic 3.0. An application typically has at least one form for the user-interface, along with some code to do any work that needs to be done, and the application probably talks to a database to store and retrieve data.
To keep this very simple, let's build a quick Visual Basic application that has a single form and uses a simple text file as a logical database. This example demonstrates the distinct layers of an application.
We'll just have a simple form, as shown below. The text box is called txtName
and the command buttons are cmdGet
and cmdPut
:
Let's suppose that we have two business rules. The first is that the name must be in uppercase, and the second is that the name is required.
To ensure that the name is uppercase, we'll add the following code:
Option Explicit
Private Sub txtName_LostFocus()
txtName.Text = UCase$(txtName.Text)
End Sub
And to make sure we don't save a blank name, we'll add this code:
Private Sub cmdPut_Click()
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
End Sub
Now let's just add some code to save the name to a file. This would normally be saving to a database, but we want to keep the program very simple to illustrate the different processes that are happening:
Private Sub cmdPut_Click()
Dim lngFile As Long
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
lngFile = FreeFile
Open "C:\TEMP.TMP" For Output As file
Print #lngFile, txtName.Text
Close lngFile
End Sub
Finally, we'll add some code in behind cmdGet
to pull the name back in from our 'database':
Private Sub cmdGet_Click()
Dim lngFile As Long
Dim strInput As String
file = FreeFile
Open "C:\TEMP.TMP" For Input As lngFile
Line Input #file, strInput
txtName.Text = strInput
Close file
End Sub
Now run the program. As you can see, we're able to store and retrieve a name. Furthermore, we won't save a blank name, and any name that we do save is stored in uppercase.
Now let's examine the program from the viewpoint of a three-tier logical model. We'll try to determine which parts of the program perform the presentation, which parts the business logic and which parts the data processing.
This is really the user-interface, or how the program presents information to the user and collects information from the user. In our simple example, the presentation layer certainly includes the form itself:
The presentation layer also includes some of the code that we put behind the form, but certainly not all the code is directly related to the presentation. In the following listing, the presentation code is highlighted:
Option Explicit
Private Sub cmdGet_Click()
Dim lngFile As Long
Dim strInput As String
lngFile = FreeFile
Open "C:\TEMP.TMP" For Input As lngFile
Line Input #file, strInput
txtName.Text = strInput
Close file
End Sub
Private Sub cmdPut_Click()
Dim lngFile As Long
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
lngFile = FreeFile
Open "C:\TEMP.TMP" For Output As lngFile
Print #lngFile, txtName.Text
Close file
End Sub
Private Sub txtName_LostFocus()
txtName.Text = UCase$(txtName.Text)
End Sub
Out of all that code, almost none of it is involved in actually presenting data to the user. Take a look at the lines that we've highlighted. For instance, we need to display the name after we've retrieved it from storage:
txtName.Text = strInput
Notice, however, that this line is stuck right in the middle of several other lines of code that have nothing to do with presenting the data.
Detecting that txtName
is blank is a business rule, so that is not part of the presentation. Notifying the user that it is blank, however, is indeed part of the user interface:
MsgBox "You must enter a name", vbInformation
Again, our presentation code is in the middle of other code that is not interface-related. The upshot of this is that if we want to change the business rule, or we want to change the presentation, then we're going to be changing the same code block for either task.
That wasn't too bad, but the line in the LostFocus
event is not so cut and dried. This line does two things: it converts the name to uppercase (a business rule), and it displays the name back into the form so that the user can see the final result (a presentation choice):
txtName.Text = UCase$(txtName.Text)
Looking at these lines of code, you can see just what a mess we've gotten ourselves into. We have no way of changing the interface without tampering with business-related code, and we can't change our business rules without affecting the interface code.
Let's now look at the parts of the program that handle the business logic or business rules. Again, it's only a part of the overall program, so we'll highlight the areas that we need to examine:
Option Explicit
Private Sub cmdGet_Click()
Dim lngFile As Long
Dim strInput As String
lngFile = FreeFile
Open "C:\TEMP.TMP" For Input As lngFile
Line Input #lngFile, strInput
txtName.Text = strInput
Close file
End Sub
Private Sub cmdPut_Click()
Dim lngFile As Long
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
lngFile = FreeFile
Open "C:\TEMP.TMP" For Output As lngFile
Print #lngFile, txtName.Text
Close lngFile
End Sub
Private Sub txtName_LostFocus()
txtName.Text = UCase$(txtName.Text)
End Sub
In the cmdPut_Click
event, we need to make sure that the name is not blank - and avoid saving the value if it is:
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
Once again, notice how there is code managing the presentation layer right in the middle of our business processing.
The situation is worse (but now rather familiar) within the LostFocus
event: we see the same line that we saw in the presentation layer, now working for our business processing to make sure that the name is in upper case:
txtName.Text = UCase$(txtName.Text)
Furthermore, suppose we need to provide access to this name to some other program, or from some other part of the same program: since the business logic is so tied into the display of this form, we'd have to duplicate the business code elsewhere.
Clearly, this is not an ideal way to organise an application.
In our simple example, the data service code (highlighted in the following code) is fairly straightforward:
Option Explicit
Private Sub cmdGet_Click()
Dim lngFile As Long
Dim strInput As String
lngFile = FreeFile
Open "C:\TEMP.TMP" For Input As lngFile
Line Input #lngFile, strInput
txtName.Text = strInput
Close file
End Sub
Private Sub cmdPut_Click()
Dim lngFile As Long
If Len(Trim$(txtName.Text)) = 0 Then
MsgBox "You must enter a name", vbInformation
Exit Sub
End If
lngFile = FreeFile
Open "C:\TEMP.TMP" For Output As lngFile
Print #lngFile, txtName.Text
Close file
End Sub
Private Sub txtName_LostFocus()
txtName.Text = UCase$(txtName.Text)
End Sub
Still, in both the cmdGet_Click
and cmdPut_Click
event code, the data processing is mixed right in with the presentation and business logic, making it difficult to change the data processing without risking the other parts of the program.
Certainly, there are conventional solutions to help deal with these problems - including putting code in BAS modules, or creating class modules to hold this code. Unfortunately, these solutions don't fully meet the needs of code reuse and separating (or partitioning) the various parts of the application.
The ideal solution is to pull the user-interface code into one area, the business logic into another and the data processing into a third. By breaking our application into these tiers, we can make it much easier to change any section of our program with less risk of causing bugs in the other tiers. By avoiding the case where a single routine includes code for both presentation and business logic for instance, we can change our presentation code without impacting the business code itself.
We can also facilitate code reuse. For instance, the code in the business tier may be useful to more than one form in the user-interface tier. If our business code is intermingled with the presentation code, it can be very difficult to use that business logic across various different presentations. By separating the code, we can make it very easy for our application to show the same business information through various presentations.