Generating Main Business and Interface Code

[Previous] [Next]

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:

  • HorseMaintInterfaces, for our COM interfaces. We assigned the following classes to this component:
    • IViewer
    • IMaint

  • HorseMaintEntities, for our Entity Management classes. We assigned the following classes to this component:
    • HorseManager
    • CountryManager
    • TrainerManager

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.

click to view at full size.

Figure 10-1. Two new components realize the Business Entity Interfaces and the Business Entity Management classes, respectively.

Generating Interface Code

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.

Looking at the IMaint code

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:

  • Since this is a generalized GetById method contained in an interface rather than in a specific class, it needs to be able to accept several kinds of key values. Some keys are numeric, others are strings, and others again consist of several values in combination. Only Variant variables can take any kind of value or even sets of values. Since the vntId argument is a Variant, it can take any kind of key. It's up to the implementing class to parse the key, setting up a contract with its clients concerning the kinds of key values it will accept.
  • Since this method returns a Variant, it can return data in any form needed by the implementing class. The intReturnType argument allows clients to ask for a certain type of return value. Since it's optional, the client doesn't have to bother with it if it accepts a default return type. This allows different clients to have different return types; clients currently being developed might receive recordsets, while clients developed next spring might receive XML data sets.

Looking at the code for the IViewer interface

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.

Generating Entity Management Code

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.

Getting the Country List

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.

Implementing 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 first part of the function name, before the underscore, is the name of the interface the code will implement: in this case, the IViewer interface.
  • The second part is the name of the method it will implement: in this case, GetList.

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.

Interface and implementation methods

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:

  • The class itself privately owns the GetAllAsRS method. This method has nothing to do with the interface except that it helps implement the interface.
  • The private method takes no arguments at all, whereas the interface event procedure takes two, the second of which, the optional intReturnType argument, determines whether to return a recordset, an XML document, or something else that we don't even know anything about until the need for it arises.
  • The interface event procedure returns a Variant; this means that it implicitly can return either a recordset or an XML document. The private method in contrast explicitly returns a recordset. It returns this recordset to the interface event procedure, which in turn returns the recordset as a Variant to its client.

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.

Enhancing the Code

Now it's time to add manually written code that will help implement the interface. We'll do it this way:

  1. First we'll make sure that the interface event procedure calls the method that contains the implementation code.
  2. Then we'll move the test stub we created for the facade object to the entity management class.
  3. Finally we'll make the facade object call the main entity management class.

Calling the private method

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:

  • If the intReturnType argument equals the ADBReturnTypeRS constant, presumably equal to 0, a method that provides an ADO recordset is called.
  • If it equals ADBReturnTypeXML, another method that provides an XML data set is called. (This method doesn't yet exist, so the method generates an error for the time being.)
  • If it doesn't equal either value, the client has supplied an invalid intReturnType and our code raises another error. It's not Visual Basic but the function itself that raises this error.


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