Getting a List of Horse Names

[Previous] [Next]

The next thing we need is a list of horse names. This job is a bit more difficult because it involves a filtering condition. We can't, or at least shouldn't, move all the horse names to the client; they're too many for that. In our test form as well as in our prototype, we can specify a name pattern for the filtering of horse names, and such a filtering condition will be easy enough to implement.

However, other use cases will probably need other ways of filtering horses for name lists. Here are a few examples, most of which will sometimes be used on their own and other times in different combinations:

  • Horses of a certain age
  • Horses of a certain sex
  • Horses that are still active
  • Horses that are trained by a certain trainer

When you specify the structure of your filtering variable, you should make allowances for filtering conditions other than the one, the name pattern, that you immediately need.

The example we'll show you now is simplistic, and you'll probably need more complex filtering in real-world applications. We assume for the purposes of our example that whenever there's more than one filtering condition, all of them should be ANDed together. In other words, our example doesn't support OR.

For your own purposes, you can take this idea as far as you want. Rather than complicate it too much, however, you'd do well to consider adding interfaces to a class, interfaces that cater to specific needs. Making your solutions too complex is seldom your best option.

Anyway, here's what we did:

  • We specified that the vntConditions argument should always be an array.
  • The first column element should specify which kind of condition the row element would represent.
  • The second column element should specify type of comparison. Examples of comparison types are =, >, <, and Like.
  • The third column element should specify the value to compare with.

In the first version, we would support only name pattern filtering. The array, then, would always be one row only, and the first column would always specify Name. Any other structure would be considered illegal in the first version of our code. Since everything in the class is private, published through separate interfaces, we're absolutely free to expose minimum functionality and then enhance it. We can do all this without changing the interface.

So here's a typical vntConditions array for our first version:

Name     Like     So%

This example would result in a list of all horses with names starting with So.

In a later version (not much later, mind you), we would use the following example to get the names of all three-year-old colts with names starting with So:

Name     Like     So% Age      =        3 Sex      =        h2

Modifying the Facade

Taking all the preceding issues into account, here's the modified facade code. You'll notice that once again, the facade class simplifies things for the client and the client developer. Rather than having to cope with the complexities of the vntConditions argument, the client code just sends a string with a name pattern.

The client doesn't even have to care about XML or ADO: this user application expects to receive its data in the form of ADO recordsets, and that's what it gets. The client will have no worries about that: the facade object handles those issues for it. Here's the code:

Public Function GetHorseList(ByVal strNamePattern As String) _ As Recordset Dim objHorses As HorseMaintInterfaces.IViewer Dim vntConditions(0, 2) As Variant vntConditions(0, 0) = "Name" vntConditions(0, 1) = "Like" If Right$(RTrim$(strNamePattern), 1) = "%" Then vntConditions(0, 2) = RTrim$(strNamePattern) Else vntConditions(0, 2) = RTrim$(strNamePattern) + "%" End If Set objHorses = CreateObject("HorseMaintEntities.HorseManager") Set GetHorseList = objHorses.GetList(vntConditions) End Function

Please remember that in the server, ADBReturnTypeRS has the default value 0. So the facade won't have to specify it as long as it expects the data set returned to be an ADO recordset.

Please also note that the facade object exposes its default interface to clients. It serves its clients by handling the separate COM interfaces of the main business objects for them. Therefore, any kind of client can use this server application, which is of special value to script clients. Such clients are, as you know, not easily capable of using any interface other than the default interface.

Thanks to the facade tier, you as a developer are free to use separate interfaces in the main business tier, thus vaccinating your main business objects against future changes in requirements.

The Horse Manager Class

As in the other classes, the interface procedures in the HorseManager class decide which private implementation method to call:

Private Function IViewer_GetList(ByVal vntConditions As Variant, _ Optional intReturnType As Integer) As Variant Select Case intReturnType  Case ADBReturnTypeRS ' Return a recordset ' Get horses for name pattern ' A later version will allow other filtering ' conditions as well Set IViewer_GetList = _ GetListAsRSForNamePattern(CStr(vntConditions(0, 2))) Case ADBReturnTypeXML ' Not yet implemented Err.Raise vbObjectError, "HorseManager, IViewer_GetList", _ "XML method not yet available" Case Else ' Invalid intReturnType Err.Raise vbObjectError, "HorseManager, IViewer_GetList", _ "Return type supplied is not valid" End Select End Function

Since this code is private, it's perfectly all right to modify it to allow for other filtering conditions. As a matter of fact, we have to do that for our project because other use cases require other combinations of filtering conditions. For now, all we have to do is implement this special case: returning a list of horses, selected according to a given name pattern, in the form of an ADO recordset.

Here's the test stub code for the private method that implements all this:

Private Function GetListAsRSForNamePattern _ (ByVal strNamePattern As String) As Recordset Dim rs As ADODB.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 rs.Open rs.AddNew rs!HorseId = 10 rs!HorseName = "Nijinsky II" rs.AddNew rs!HorseId = 20 rs!HorseName = "Nearco"  Set GetListAsRSForNamePattern = rs End Function

We have only a few remarks to make about this piece of code:

  • It's the test stub, moved from the facade to here, and it's nearly unchanged. The only line we had to change was the last one, setting the function to return the recordset.
  • If you're really observant, you might have noticed that we changed the name of this method, adding ForNamePattern to the previous name. In our Rose model, this method still goes under its former name: GetListAsRS. This is no problem. We'll soon update the model from our code, thus automatically updating the model with any relevant code changes we have made. Then they'll again be synchronized with each other.
  • We might change the name again, and we're quite likely to do that when we take care of other requirements from other use cases. At least one of these use cases includes a requirement to list horses of a certain age, a certain sex, and a certain name pattern. When we see that, we'll probably decide not to create a specific method for each of all the possible combinations of these conditions. Instead, we'll probably enhance this one. If we do, the name we now give it will no longer be a proper name for it. This is no problem either. You can change private methods, properties, and events as much as you like, as long as your classes honor all the interfaces they expose.

Getting a Single Horse

The next task is to get data on a single horse from the database. With the experience we now have, this is dead easy. Let's just take a fast look at the code for it. You'll see that we won't need to explain much here.

We'll start, as usual, with the facade code. Its only purpose is to delegate the job to a proper method in the HorseManager class. Note that this time we use the IMaint interface rather than the IViewer interface we've used so far:

Public Function GetHorseById(lngId As Long) As Recordset Dim objHorses As HorseMaintInterfaces.IMaint Set objHorses = CreateObject("HorseMaintEntities.HorseManager") Set GetHorseById = objHorses.GetById(lngId) End Function

Then comes the interface event procedure. It looks very much like the ones we've used before:

Private Function IMaint_GetById(vntId As Variant, _ Optional intReturnType As Integer) As Variant Select Case intReturnType Case ADBReturnTypeRS ' Return a recordset Set IMaint_GetById = GetByIdAsRS(CLng(vntId)) Case ADBReturnTypeXML ' Not yet implemented Err.Raise vbObjectError, "HorseManager, IViewer_GetById", _ "XML method not yet available" Case Else ' Invalid intReturnType Err.Raise vbObjectError, "HorseManager, IViewer_GetById", _ "Return type supplied is not valid" End Select End Function

Finally, the implementation in the private GetByIdAsRS method is just the test stub code that we moved from the facade:

Private Function GetByIdAsRS(lngId As Long) As Recordset Dim rs As ADODB.Recordset, fld As ADODB.Field Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 rs.Fields.Append "BredIn", adVarChar, 3 rs.Fields.Append "Sex", adVarChar, 1 rs.Fields.Append "Birthyear", adSmallInt, 2 rs.Fields.Append "Trainer", adInteger, 4 rs.Open rs.AddNew rs!HorseId = lngId rs!Sex = "h" rs!Birthyear = 1988 Select Case lngId Case 10 rs!HorseName = "Nijinsky II" rs!BredIn = "GB" rs!Trainer = 16 Case 20 rs!HorseName = "Nearco" rs!BredIn = "USA" rs!Trainer = 20 Case Else rs!HorseName = "Somebody else" End Select Set GetByIdAsRS = rs End Function



Designing for scalability with Microsoft Windows DNA
Designing for Scalability with Microsoft Windows DNA (DV-MPS Designing)
ISBN: 0735609683
EAN: 2147483647
Year: 2000
Pages: 133

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