Later in this chapter we're going to create business objects that are specific to a particular application. However, before we get to that point, we need to design business objects and data-access base classes to use for deriving our application-specific business objects. Although the .NET Framework class library contains many great classes, there is no business object base class. This means you'll have to roll your own or buy a third-party business component. In either case, there is some important functionality you should expect from a business object base class. As menti oned earlier, a business component should be able to access different types of data. A good way to accomplish this is by placing all data access logic in data-access classes rather than in the business object itself. When a business object gets a request for data, it can pass the call to a data-specific data-access object that contains the actual data-access logic. We can create a family of data-specific classes for accessing SQL Server, Oracle, and so on. This allows us to attach the data-access classes we need to the business object at run time. We'll take you step by step through the process of modeling these data access classes in Visio to show you how it's done. We'll go into greater detail creating these first classes, but later in the chapter we'll assume you are familiar with the process described here. To begin, launch Visio and create a new UML Model Diagram. This opens a new, blank diagram in the Visio IDE and displays a new Top Package node under the Static Model node of the Model Explorer as shown in the following image.
The Model Explorer displays your UML model as a hierarchical tree view. As described in the next section, folders in this tree view can correspond to a namespace hierarchy. You can add diagrams, sub-packages, subsystems, classes, interfaces, data types, actors, and use cases diagrams to a package. Each of these elements is represented by a different icon in the tree view. You can right-click on these elements to delete, rename, and edit them, or you can drag and drop them on various diagrams. In the next section, we will create packages representing namespaces in .NET.
Creating Namespace PackagesIn .NET, namespaces provide a means for categorizing your classes and creating unique class names. In Visio, packages perform a similar function. Packages, represented as folders in the Model Explorer, can be used to represent namespace hierarchies. In the case of our data-access classes, we'll create a package structure representing a Wrox.UMLDotNet.Data namespace. According to Microsoft's naming convention, the first part of a namespace should be your company name (we'll use Wrox for this example), the next part should be your product (this book, UMLDotNet), and then any additional name segments to further categorize your class. To create this package structure in Visio:
Documentation is critical to a good object model. Regardless of how descriptive your package, class, and member names are, you need to document your intent for those who read and interpret your model. Although your design may seem self-evident at that time you create it, a few weeks or a few months later I guarantee that good comments will save you hours of rethinking and head scratching!
Creating an Abstract Data-Access ClassBecause there are a variety of ways to access data, it makes sense to model an abstract data access base class defining an interface for a family of data access classes. We can then model concrete subclasses that access specific kinds of data. To create a new class belonging to the Wrox.UMLDotNet.Data namespace, add the class to the Wrox | UMLDotNet | Data package in Visio:
When the dialog closes, look in the Model Explorer and you will see a new DataAccessBase class node as shown in the following screenshot. We can see that the classes added to a package belong to the namespace represented by the package hierarchy.
Creating a Class (Static Structure) DiagramNext, let's create a new class diagram beneath the Top Package node to display the classes we create. Class diagrams are a type of static structure diagrams that provide visual representations of your classes. They allow you to model class attributes, operations, associations, interfaces, and dependencies.
A few things to note - the class name is shown in italics. The UML specification dictates that abstract class names be displayed in italics. The Data: prefix indicates this class is found in the Data package. This is a good time to save your Visio UML Model. Select File | Save from the menu to display the Save As dialog. Specify the name you want to give this file and the directory where you want to save it. If this is the first time you've saved the diagram, a Properties dialog displays prompting you to specify information about your model. In the Summary tab, you can specify information such as a title, subject, author, manager, and company. You can also specify a relative path for hyperlinks and how you want to display a preview of the model. The Contents tab displays each page of the model and the master shapes on them. For more information on this dialog, click the Help icon at the bottom left corner of the dialog. For now, just change the Title to My Sample Model, and then click the OK button.
Adding Operations to the ClassNow let's add a few basic operations to the DataAccessBase class. An operation in a UML class is implemented as a method in a real-world .NET class, as shown in the table earlier in this chapter. In Visual Basic .NET, operations that do not return a value are implemented as subroutines (Sub), whereas operations that do return a value are implemented as Functions. In C#, there is only one kind of method - methods that do not return a value simply return void. This isn't a study in data access, so we won't go into too much detail here. We'll just add a few operations allowing us to retrieve and save data in a DataSet. Both operations will accept a database key used to look up database connection information in a configuration file:
In a production data access class, we would also add a number of methods to delete records, work with stored procedures, and typed DataSet objects. For this example, the two methods shown above are sufficient. Now let's add these two operations to our abstract DataAccessBase class. The implementation of these operations is different for each concrete subclass (there is different code in the methods of each subclass), so we'll make them abstract. This also guarantees any future subclasses will implement these operations, because .NET compilers enforce implementation of inherited abstract methods.
The UML Class Properties DialogIn Visio, you add a new operation to a class by using either the UML Class Properties dialog or the UML Operation Properties dialog (discussed in the next section). To launch the UML Class Properties dialog, you either right-click the DataAccessBase node in the Model Explorer or right-click the class on the static structure diagram and select Properties from the context menu. When the dialog appears, select the Operations item in the Categories pane on the left and the Operations grid is displayed on the right as shown in the following screenshot - from here we will be able to add new operations to a class.
Here is a description of each column in the Operations grid:
The UML Operation Properties DialogAlthough this dialog can be used to add new operations, it doesn't include entry fields for some very basic operator information such as a description and parameters. This is where the UML Operation Properties dialog comes to the rescue. To launch this dialog, click the New button from the UML Class Properties dialog, which adds a new record to the Operations grid, and then click the Properties button. As you can see in the following image, this dialog allows you to specify more information than the previous dialog to better define your class operations.
Here is a description of each entry field in this dialog (some of these fields are the same as those in the Operations grid described previously):
Follow these steps to add a new GetDataSet operation to the DataAccessBase class:
Specifying Operation ParametersNow let's add parameters to the operation. Previously, we determined this method accepts two string parameters-a SQL SELECT string and a database key.
The Parameters grid allows you to view and specify operation parameters. You may be surprised to see a parameter already shown in the grid. If you look in the Kind column, you can see that this is the return value that we specified for this operation.
In the Kind combo box, there are three choices - in, out, and inout.
Leave the Kind combo box set to in.
You should now see three parameters for the operation as shown in the following image. Note that both the operation return values and parameters are displayed:
Marking an Operation as AbstractAs we mentioned earlier, the GetDataSet method is supposed to be abstract. To mark it as such, select Method in the Categories pane to display the controls shown in the following image.
This brings up the question of what the difference is between an operation and a method. When you implement classes in code, methods are added to the class for every operation specified in the class model.
The following table shows how different basic UML class elements map to .NET code:
Unlike concrete operations, abstract operations have no corresponding concrete method, just a method signature, which includes the method name, its parameters, and their types. The Has method checkbox is selected by default, indicating that the operation selected in the Operation name combo box will have a corresponding method in the realized class. Because this is an abstract operation that will not have a corresponding method containing code, let's uncheck this box. When you clear this checkbox, the Language and Method body fields are disabled indicating there is no code associated with this operation. If this box is unchecked, when you generate code from the class, the resulting method is marked as abstract. Click OK to close the UML Operation Properties dialog, and then click OK again to close the UML Class Properties dialog. The visual representation of your DataAccessBase class should look like the following image:
Notice the GetDataSet method is shown in italics per the UML specification for abstract operations. The class diagram shows the GetDataSet operation signature, including the parameter names and their types. In the next section, we'll take care of the method return value being marked unspecified.
Adding .NET Base Classes to the ModelThere isn't a DataSet class in our model, so we weren't able to specify the GetDataSet operation returns a DataSet. If you don't have .NET base classes in your model, you're not able to specify the base classes from which your custom classes are derived, and you can't specify parameters and return values that are .NET base class types. Although we discuss an interesting way around this problem in Chapter 5, for the purposes of this chapter, let's address this problem by adding a DataSet class to our model. The DataSet class belongs to the System.Data namespace, so we need to add System | Data packages to the Top Package node in our diagram.
When you're done, your model tree should look like the following diagram.
Now that we have a DataSet class, we can go back into the definition of the GetDataSet method and specify a return value of DataSet. The easiest way to do this is to expand the DataAccessBase node in the Model Explorer, and double-click the GetDataSet operation. This launches the UML Operation Properties dialog with the GetDataSet operation ready for editing. In the Return type combo box, select Data::DataSet, and click OK to close the dialog.
Adding the SaveDataSet OperationNow, let's add our second SaveDataSet operation to the DataAccessBase class. This time, we'll launch the UML Operation Properties dialog directly, without first going through the UML Class Properties dialog. To do this:
Your DataAccessBase class on the class diagram should now look like the following image. Note the full signature of class operations is displayed:
If you expand the GetDataSet and SaveDataSet operation nodes of the DataAccessBase class in the Model Explorer, you'll see three parameters listed under each as shown in the following diagram. The GetDataSet and SaveDataSet parameters represent the return values of their respective operations.
If you double-click a parameter node (or right-click the node and select Properties from the context menu), it launches the UML Parameter Properties dialog allowing you to view or edit the parameter's properties. You can easily add new parameters to an operation by right-clicking the operation node and selecting New | Parameter... from the context menu. You can also delete a parameter by right-clicking it and selecting Delete from the context menu, or by selecting the parameter and pressing the Delete key.
Creating Concrete SubclassesNow that we have an abstract data access class, we can create a few concrete subclasses. We'll create these classes in quick succession and then show you how to make them subclasses of the DataAccessBase class. You create these classes using the UML Class Properties dialog that is launched by right-clicking the Top Package | Wrox | UMLDotNet | Data package in the Model Explorer and selecting New | Class from the context menu. Set the following name and description for each of these classes (you can accept all other default settings):
Now we can specify that these new classes are derived from the DataAccessBase class. To do this, drag and drop each of these classes onto the class (static structure) diagram. Next, make sure the UML Static Structure stencil is open as shown in the following diagram - this stencil contains shapes you can use on your static structure diagrams. Now drag and drop a Generalization shape (a line with a single, solid arrow head) onto the diagram for each subclass. In Visio, when you want to add two or more instances of the same shape to a diagram, just drag and drop the first shape, and then press Ctrl+D for each additional shape you want to add.
For each Generalization shape, connect the arrowhead to the bottom of the DataAccessBase class, and connect the other end of the arrow to the top of a concrete subclass. When you're done, your class diagram should look like the following image.
Now we have three concrete data-access classes to use for accessing different types of back-end data. Depending on the type of data that a business object needs to access, it can instantiate one or more of these classes. These classes act as wrappers for the different .NET data provider classes, giving us some important advantages. First of all, these classes encapsulate the complexity of ADO.NET classes, by allowing us to call higher-level methods that sweat the details of ADO.NET for us behind the scenes. Since our data access classes are all derived from the DataAccessBase class, it also allows the business object to reference any concrete data-access class the same way - by treating it as if it is of the type DataAccessBase. This allows us to have generic code throughout our business object rather than SQL Server, Oracle, or OLE DB-specific code. Each of these new concrete subclasses inherits the GetDataSet and SaveDataSet operations declared in the DataAccessBase class, and this is reflected in the Visio model. To see this, right-click the DataAccessSql class node and select Properties from the context menu to launch the UML Class Properties dialog. In the Categories pane, select Operations. We haven't added any custom operations to the DataAccessSql class, so there are no operations listed in the grid. However, at the bottom of the Operations grid is a tab selector (similar to Excel's worksheet selector) that lists all of the classes in the DataAccessSql hierarchy. If you select the DataAccessBase tab, you see the GetDataSet and SaveDataSet operations belonging to the DataAccessBase class displayed in the grid as shown in the following image:
Creating a Business Object Base ClassNow that we have a family of data access classes, we're ready to create a business object base class. One of the first things we need to decide is the .NET type to use as the base class for our business object class.
Choosing a .NET Type for Your Business Object ClassA great candidate for our business object's base class is the MarshalByRefObject type, because classes derived from this type can be accessed remotely. When creating n-tier applications, assemblies containing business objects are often placed on application servers and accessed from workstations. Using MarshalByRefObject gives our business objects this functionality. Based on this, we need to add the MarshalByRefObject class to our Visio model:
If you'd like, you can add all classes in the hierarchy for MarshalByRefObject as well as the DataSet class we added earlier. The MarshalByRefObject class derives directly from System.Object. You can add the Object class directly to the System package in the Visio model. The DataSet class derives from the System.ComponentModel.MarshalByValueComponent, which in turn derives from System.Object. To add this class to the model, you need to add a ComponentModel package below the System package (representing the .NET System.ComponentModel namespace), and then add a MarshalByValueComponent class to the package. After you've added these classes, your System package should look like the following image:
You can also add a new static structure diagram to the Visio model (named something like .NET Base classes) where you specify the generalization relationships between these classes. You can add this diagram to the model by right-clicking the Top Package node and selecting New | Static Structure Diagram from the context menu. This diagram should look like the following image:
Although we've added these .NET base classes to our model to show how it's done, rather than creating your Visio UML models from scratch, you should create them from a Visio model that already contains most of the common .NET base classes. You can create such a template model yourself - in Chapter 5, where we look at reverse engineering code with Visio, we see how to create limited models of the .NET base classes.
Creating the BusinessObject ClassNow we're ready to create our business object base class.
When you're finished, the static structure diagram should look like this (not including the data-access classes):
Specifying a Composition RelationshipNow we can use the class diagram to specify the association between the business object and the data-access classes. There are many associations between classes that can be modeled in the UML. One such relationship is aggregation, which is a whole-part relationship. This sort of relationship exists between our business object and data-access classes - but with a special twist. In this aggregation, there is strong ownership between the business object and data-access class - the data-access class lives and dies with the business object class. This special form of aggregation is known as composition. To define a composition association between the business object and data-access classes, drag a Composition shape from the UML Static Structure shapes stencil, and drop it on the class diagram. Attach the diamond to the business object class and attach the other end of the composition shape to the DataAccessBase class. When you're finished, your diagram should look like the one shown in the following image (we've left out the BusinessObject class hierarchy):
In the UML, a filled-in aggregation diamond indicates composition. The default one-to-many multiplicity, indicated by the 1 (one) and asterisk (*), is exactly what we need for the BusinessObject and DataAccessBase classes.
To do this, double-click the aggregation shape to launch the UML Association Properties dialog as shown in the following screenshot:
Normally, you don't need to give the association or its end points names, so we'll leave the association name set to its default value, and we'll simply remove the end-point role names. To clear the names, remove the End1 and End2 text from the records in the Association Ends grid. You can also click the Properties... button and edit each association end point individually in the UML Association End Properties dialog that provides access to additional properties as shown in the following image:
Regardless of the method you choose to remove the end points, when you click OK to save changes from the UML Association Properties dialog, the end-point names are no longer visible in the class diagram.
Adding Overloaded BusinessObject OperationsNow we're ready to specify some basic data-access operations for our business object base class. We will add GetDataSet and SaveDataSet operations to the BusinessObject class corresponding to methods of the same name on our data-access classes. When these methods are called on the business object class, the business object passes the call to an associated data-access class. Because we have a family of data-access classes, we can vary the type of data we work with by using different concrete data-access classes. For example, if we want to access data in an Oracle database, the business object passes the call to an instance of the DataAccessOracle class. If we want to access data in a SQL Server database, the business object passes the call to an instance of the DataAccessSql class. In the business object class, we'll create a few different implementations of the GetDataSet and SaveDataSet methods that provide an opportunity to learn how overloaded methods are modeled in a UML class diagram.
Adding the GetDataSet OperationsNow we're going to add several operations to the BusinessObject class, so let's launch the UML Class Properties dialog. There are four ways to launch this dialog:
As a refresher, here are the basic steps for adding a new operation:
And the steps for adding parameters to an operation:
With this knowledge in hand, add three operations named GetDataSet to the BusinessObject class. These operations should all return a DataSet and their visibility should be set to protected. Also, specify the operation description, parameters, and parameter descriptions (shown in parentheses) listed in the following table:
In this section, we've added three overloaded GetDataSet operations to the BusinessObject class. These operations allow us to retrieve data by passing a SQL SELECT string and optionally, a database key and table name.
Adding the SaveDataSet OperationsNow add three overloaded operations named SaveDataSet to the BusinessObject class. These operations should all return an integer and their visibility should be set to protected. Also, specify the operation description, parameters, and parameter descriptions (shown in parentheses) listed in the following table:
When you're done, the BusinessObject class located on the class diagram should look like the following image:
Both the GetDataSet and SaveDataSet operations have overloads allowing you to pass a database key and a table name. Typically, in a .NET application, we store database information in an XML configuration file, which allows us to retrieve database information such as connection strings. If we have more than one database in our application, the database key allows us to specify which database we want to retrieve information for from the configuration file. The table name parameter specifies the name of the DataTable you want to work with. The sharp sign (#) indicates these operations are protected. The reason we have protected these methods is so consumers of our business objects cannot call them directly. For example, if we make the GetDataSet operations public, it provides an opening for consumers of the object to pass any SQL SELECT command conceivable and return secure data stored in the database. Later we'll add higher-level public methods to the business object passing predetermined SQL SELECT strings to these operations. In a more robust data-access model, we would also provide methods that allow us to call stored procedures. However, for this simple model our existing methods will suffice.
Adding Attributes to the BusinessObject ClassNext, we are going to add two attributes to the BusinessObject class - DatabaseKey and TableName. These attributes hold default values in cases where the database key and table name are not specified in calls to the GetDataSet and SaveDataSet operations. For example, if a call is made to the overload of GetDataSet that has only a single command parameter, default database key and table name values can be retrieved from the DatabaseKey and TableName attributes.
You add new attributes to the BusinessObject class using the UML Attribute Properties dialog. If you are adding only one attribute, you can launch this dialog by right-clicking the BusinessObject class in the Model Explorer and selecting New | Attribute from the shortcut menu. However, we want to add two properties, so we'll launch this dialog from the UML Class Properties dialog. To do this, double-click the BusinessObject class node in the Model Explorer. In the Categories pane of the UML Class Properties dialog, select Attributes. Click the New button, which adds a new record to the Attributes grid, and then click the Properties button to launch the UML Attribute Properties dialog shown in the following image.
The following table describes some of the more important entry fields in the UML Attribute Properties dialog and how they relate to .NET languages such as C# and Visual Basic .NET:
Now follow the same steps outlined above and add a second protected string attribute named TableName, with the following documentation:
When you're done, click OK to save changes, and then click OK to close the UML Class Properties dialog. The BusinessObject class located on the class diagram should now look like the following image:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||