To generate code for the main business interfaces and the classes, you must first create two new components, one for the interfaces and one for the classes. If you use Rational Rose (as we do) or Microsoft Visual Modeler, it makes sense to create the components in the model and then generate Visual Basic code from the model. We added the following two components to our model:
Figure 10-1 shows the component specification for the two new components. Before copying the screen shots, we focused on the Realizes tab to see which classes were realized by each component. Personally, neither Per nor Sten would use the word realized in this context, but it's the word used by Rational in Rose. A class that's realized by a component is contained by the component; when the component is compiled, the EXE, DLL, or OCX created by the compiler contains all the classes that are said to be realized by the component.
Figure 10-1. Two new components realize the Business Entity Interfaces and the Business Entity Management classes, respectively.
After we created the two new components, we generated code for the COM interfaces. As you know, you can use abstract Visual Basic classes—classes without any running code—to represent COM interfaces. This might not be absolutely ideal because when you compile your abstract Visual Basic classes into a COM component, you get the CoClasses as well as the TypeLib class library. This deviation from the idea of what a COM interface really is, however, is a small price to pay for the simplicity of using the same language for creating your classes as for creating your interfaces. You'll be able to create your interfaces without any knowledge whatsoever of Interface Definition Language (IDL).
Anyway, we started with the interfaces rather than with the real classes for one simple reason. The classes implement the interfaces and therefore depend on them. They can't depend on anything that doesn't exist, so it makes sense to start with the interfaces and then go on with the actual classes.
Figure 10-2 shows the Visual Basic Project Explorer for the new project. This is what you see from the Related Documents folder after code has been generated. The Related Documents folder connects the Visual Basic project to the Rose model.
Figure 10-2. Two interface classes were generated by Rose's code generator.
Let's take a fast look at the code generated for us. We'll do that without going too much into the details of it.
Here's the code for the IMaint interface class with its four methods:
Option Explicit 'The GetEmpty method returns an empty recordset, designed 'to accept data for a new object that's to be inserted in 'the database. The data type of the return value is set to 'Variant to allow for future versions of the implementing 'classes to return, for instance, an XML data set instead. 'For the same reason, we use the optional intReturnType 'argument, which lets the client application ask for whatever 'kind of return type the implementing class will support. 'Arguments: '========= 'Optional intReturnType : integer 'Returns: '======= 'A Variant containing an empty recordset that will include 'a set of fields to be determined by the implementing class. 'See preceding comment about possible future XML support. ' '##ModelId=374A644102EA Public Function GetEmpty(Optional intReturnType As Integer) _ As Variant '## Your code goes here.... End Function 'The Delete method takes an object ID that represents a 'single object of the implementing class to be deleted. 'Arguments: '========= 'vntId : Variant 'Returns: '======= 'Nothing ' '##ModelId=374A644102D5 Public Sub Delete(vntId As Variant) '## Your code goes here.... End Sub 'The Save method takes a recordset containing data about 'a single object only. The implementing object normally 'sends this recordset to the database for persistent storage. 'Arguments: '========= 'rs : Variant 'Returns: '======= 'Nothing 'NOTE: The rs argument data type is Variant rather than 'recordset, allowing a future version to receive an XML 'data set instead. The implementing class will be able to 'determine which type of data has been sent to it. ' '##ModelId=374A644102C1 Public Sub Save(rs As Variant) '## Your code goes here.... End Sub 'The GetById method takes an ID value and returns a recordset '(today) or perhaps an XML data set (future version) containing 'data about the identified object only. The optional intReturnType 'argument determines what to return. 'Arguments: '========= 'vntId : Variant 'Optional intReturnType : integer 'Returns: '======= 'A Variant containing an ADO recordset (today) or perhaps 'an XML data set (tomorrow). The recordset (or data set) 'contains one record, the structure of which is determined 'by the implementing class. ' '##ModelId=374A644102AD Public Function GetById(vntId As Variant, _ Optional intReturnType As Integer) As Variant '## Your code goes here.... End Function |
As you can see, the generated code consists of comments, taken from the operation specifications in the model, and of method signatures. The code also includes a cue for you to insert your own code, saying, "Your code goes here...." You shouldn't obey this one. Interfaces should normally be abstract and contain nothing but what you see in this example.
If you look closely at any one of the functions or subs, you'll see that these interfaces really are generalized. Let's pick one and have a look. As we have in preceding code snippets, we've presented the parts of the code we want you to focus on in boldface type:
'The GetById method takes an ID value and returns a recordset '(today) or perhaps an XML data set (future version) containing 'data about the identified object only. The optional intReturnType 'argument determines what to return. 'Arguments: '========= 'vntId : Variant 'Optional intReturnType : integer 'Returns: '======= 'A Variant containing an ADO recordset (today) or perhaps an XML 'data set (tomorrow). The recordset (or data set) contains one 'record, the structure of which is determined by the implementing 'class. ' '##ModelId=374A644102AD Public Function GetById(vntId As Variant, _ Optional intReturnType As Integer) As Variant '## Your code goes here.... End Function |
The interesting details are the following:
Here comes the code for the other interface, IViewer:
Option Explicit 'The GetById method takes an ID value and returns a recordset '(today) or perhaps an XML data set (future version) containing 'data about the identified object only. The optional intReturnType 'argument determines what to return. 'Arguments: '========= 'vntId : Variant 'Optional intReturnType : integer 'Returns: '======= 'A Variant containing an ADO recordset (today) or perhaps an XML 'data set (tomorrow). The recordset (or data set) contains one 'record, the structure of which is determined by the implementing 'class. ' '##ModelId=374A64460319 Public Function GetById(vntId As Variant, _ Optional intReturnType As Integer) As Variant '## Your code goes here.... End Function 'The GetList method takes a set of filtering conditions, stored 'in a Variant variable, and returns a recordset containing data 'about all the objects handled by the implementing class that 'will fit the filtering conditions. 'The optional intReturnType argument allows clients to ask for other 'return types, for example, XML data sets. That's why the return 'data type is Variant rather than recordset. 'Arguments: '========= 'vntCondition : Variant 'Optional intReturnType : Integer 'Returns: '======= 'A Variant containing an ADO recordset (today) or an XML data 'set (future version) that will include a set of fields to be 'determined by the implementing class. ' '##ModelId=374A64460305 Public Function GetList(ByVal vntConditions As Variant, _ Optional intReturnType As Integer) As Variant '## Your code goes here.... End Function |
This interface code follows, as it should, the same pattern as the first one. We don't need to comment further on it, and there's no code to add. Let's now take a look at those real business entity classes that will implement these interfaces.
When we went through the same procedure for our entity management classes that we did for the interface classes, we wound up with another new Visual Basic project. Figure 10-3 shows the Visual Basic Project window after code has been generated.
Figure 10-3. Three classes were generated in the HorseMaintEntities Visual Basic project. Notice that the Rose (or Visual Modeler) model is seen as a related document.
In the next section, we'll take a look at the class that our test form application will call first: our CountryManager class. Since it contains quite a bit of generated code—mostly comments—we'll show it to you one piece at a time. To make our points, we'll also boldface the lines we want you to focus on.
As we go through the code, we'll make our own comments to help you understand what we're trying to accomplish. We'll start by looking at one interface method, enhancing it with some manual code, and then we'll just give you an overview of the rest. First of all, however, let's make a comment on the code that allows this class to implement our two interfaces.
Here's the first part of the code:
'##ModelId=37677DFC01C0 Implements IViewer '##ModelId=37677DFA0394 Implements IMaint Option Explicit |
What's interesting here is that the Rose or Visual Modeler code generator (they're the same) automatically generates code that makes a class implement an interface. In the Rose model, we used a so-called generalization relationship to make Rose generate code lines such as Implements IViewer or Implements IMaint in the preceding example. There's another kind of relationship we could have used, the realize relationship that has in principle the same effect.
NOTE
You must make sure that you either set the ImplementsDelegation flag to False in the Visual Basic tab of the relationship specification dialog box or manipulate the Model Assistant, which is available for you only if you have the Rational Rose 98i or Rational Rose 2000 version.1
The next piece of code is for implementing one of the methods in one of the interfaces. The interface is the IViewer interface; the method is the GetList function. You can see that in the method signature below:
Private Function IViewer_GetList(ByVal vntConditions As Variant, _ Optional intReturnType As Variant) As Variant |
The method has been generated as a private function. Its name is formed according to the same principle as that of events in controls: Control_Event.
The similarity of the naming conventions for interface methods and events in controls is reasonable. An interface method is code that handles events. The event happens when a client calls the GetList method through the IViewer interface.
So let's look at the code for this interface event:
'The GetList method takes a set of filtering conditions, stored 'in a Variant variable, and returns a recordset containing data 'about all the objects, handled by the implementing class, that 'fits the filtering conditions. 'The optional intReturnType argument allows clients to ask for other 'return types, for example, XML data sets. That's why the return 'data type is Variant rather than recordset. 'Arguments: '========= 'vntCondition : Variant 'Optional intReturnType : Integer 'Returns: '======= 'A Variant containing an ADO recordset (today) or an XML data set '(future version) that will include a set of fields to be 'determined by the implementing class. ' '##ModelId=37679014036F Private Function IViewer_GetList(ByVal vntConditions As Variant, _ Optional intReturnType As Variant) As Variant '## Your code goes here.... End Function |
This is where you start implementing this interface method. Since it's an interface event, however, you should avoid putting much code in it. You should instead delegate the real work to one or more private subroutines or functions. Before adding code to call such a method, let's take a look at the definition of the method we'll call to have the work done.
'The GetAllAsRS method takes no arguments. It returns a recordset 'containing data about all the countries registered. 'Arguments: '========= 'None 'Returns: '======= 'An ADO recordset that contains the following fields: '- CountryCode '- Country ' '##ModelId=3749207D033C Private Function GetAllAsRS() As Recordset '## Your code goes here.... End Function |
As you can see, the GetAllAsRS method differs from the IViewer_GetList interface event procedure in three ways:
All of this indicates that the interface event procedure must be able to call the GetAllAsRS method when clients ask for a recordset with all the countries included. Probably, however, it should also be able to call some other private procedure when the client wants an XML data set, a selection of countries, or even in the distant future something entirely different.
Now it's time to add manually written code that will help implement the interface. We'll do it this way:
Given that somewhere we've declared the constants used, we can enhance our interface event procedure as follows:
Private Function IViewer_GetList(ByVal vntConditions As Variant, _ intReturnType As Variant) As Variant Select Case intReturnType Case ADBReturnTypeRS ' Return a recordset If IsEmpty(vntConditions) Then ' Get all countries Set IViewer_GetList = GetAllAsRS() Else ' Not yet implemented Err.Raise vbObjectError, "CountryManager, IViewer_GetList", _ "Filtered Recordset method not yet available" End If Case ADBReturnTypeXML ' Not yet implemented Err.Raise vbObjectError, "CountryManager, IViewer_GetList", _ "XML method not yet available" Case Else ' Invalid intReturnType Err.Raise vbObjectError, "CountryManager, IViewer_GetList", _ "Return type supplied is not valid" End Select End Function |
The Select Case structure handles various return types differently: