Have you ever wanted to learn more about the structural diagrams used in UML? This article discusses four important types. It is excerpted from Enterprise Development in Visual Studio.NET, UML, and MSF, by John Erik Hansen and Carsten Thomsen (Apress, 2004; ISBN 1590590422).
IN THIS CHAPTER, we'll introduce you to the UML diagrams known as structural diagrams, which describe the structure (static structure) of your solution. The following UML diagrams are structural diagrams:
Class diagram
Statechart diagram
Component diagram
Deployment diagram
As in Chapter 4, we’ll describe the frequently used diagrams in detail and use VEA 2003 for the exercises. These exercises are simple, yet they do teach you how to work with the UML models in VEA. Do yourself a favor and follow the exercises, especially if you’re new to UML and/or VEA.
In this chapter, you’ll see the code generation, which you didn’t see in Chapter 4. The reason for this is that all code generation is based on the static structure diagrams (class diagrams).1 You’ll also learn about stereotypes, which are basically a way to extend the UML specification by making a subtype appear as an ordinary element. At the end of the chapter, we’ll show you how .NET code maps to UML.
Class Diagrams
The class diagram is the most essential part of UML modeling, because you determine in your class diagram how the (static) structure of your solution should look, modeled at the most detailed level. In your class diagram, you can see which classes, COM+ components, ASP pages, and so on will be implemented and how they interact with each other. COM+ components, ASP pages, and so on are simply classes with another stereotype, as you’ll learn in this chapter.
When the developers develop the solution, their main source of information is the class diagram. The class diagram provides developers with detailed information about what operation code to develop, along with information about data types, parameters, and namespaces. If the developers need some supporting information about the behavior of the solution, they will look at the behavioral diagrams, such as the sequence diagrams and activity diagrams.
Another reason why class diagrams are the most important part of your UML modeling is that you can generate code only from your static structure diagrams, and your class diagrams are the only diagram type from which you’ll have a code skeleton for ready use in VS .NET.2 Class diagrams basically consist of the following elements:
Packages
Classes
Relationships
We’ll look at each of these elements in the following sections.
Packages
You’ve already been introduced to packages in Chapter 4. Packages can relate to different issues, depending on which diagram are implemented. For class diagrams, a package has two functions:
A package can be created to group related elements. This can be very useful if you want to form a general view of the model.
A package can represent a namespace.
In Exercise 5-1, you’ll create a class diagram named MyClassDiagram with a package named MyNamespace, as shown in Figure 5-1. Figure 5-1.Class diagram with a namespace (package)
EXERCISE 5-1
Open VEA, if it is not already open.
Create a class diagram named MyClassDiagram.
Create a package named MyNamespace.
Drag a class to the MyClassDiagram diagram and name this class Customer.
Drag the Customer class in the Model Explorer window onto the MyNamespace package. Notice that the Customer class placed on the MyClassDiagram diagram now has the Namespace specification attached.
NOTE We’ve noticed a bug in VEA: If you create a package in an empty model, the package classifier might not show on the class. To solve this problem, create a new package and delete the first one.
6. You can generate the code for the class and namespace by selecting UML -> Code -> Generate. In the Generate dialog box, choose Visual Basic as the target language, check the Customer check box, and specify the path as\EDWVSNETUMLMSF\Chapter05\, as shown in Figure 5-2. Click OK.
Figure 5-2.Generate dialog box with the Customer class
7. VEA now generates the file \EDWVSNETUMLMSF\Chapter 05\ TopPackage\MyNamespace\Customer.vb, containing the code shown in Figure 5-3. Double-click to open the file in VS .NET.
Figure 5-3.VB.NET code generated for the Customer class
Figure 5-4 shows the code for the Customer class generated with C# as the target language.
Figure 5-4. C# code generated for the Customer class
As you might have noticed, Package shapes are located in the Model Explorer window as well. You can see the MyNamespace package and the Top Package. As you learned in Chapter 4, the global namespace is the global package, and it is named Top Package by default. You’re allowed to have multiple nested packages. To nest a package, just drag a Package shape under another Package shape in the Model Explorer window. (In Chapter 12, you’ll learn what the other Package shapes in the Model Explorer window represent.)
The next step in your class diagram modeling is to place the classes on the diagram. You can drag the classes from the Shapes window onto the diagram, or you can right-click the package in which you want your new class created, and then choose New -> Class from the pop-up menu. If you create a new class in a package, it’s automatically related to that namespace (package). If you double-click a class, you’ll see the UML Class Properties dialog box, as shown in Figure 5-5.
Figure 5-5.UML Class Properties dialog box for the Customer class
The basic settings for a class are the class name and optionally a class stereotype. We’ll cover the stereotype concept in the “Stereotypes” section later in this chapter. For now, just consider stereotypes as a way of specifying a commonly occurring subtype of an element. For instance, your class can be of the stereotype COM+, indicating that it’s a class specialized to type COM+.
Classes are represented by a box shape, which is divided into three parts:
Name
Attributes
Operations
The name part is self-explanatory. The following sections describe the attributes and operations.
Attributes
Attributes are also known as properties in .NET development. You specify the attributes for the class by selecting Attributes from the Categories list of the UML Class Properties dialog box (see Figure 5-5). In Exercise 5-2, you’ll specify attributes for the Customer class. We’ll make the Customer class attributes public, in order to make them accessible to the users of the class.
EXERCISE 5-2
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Attributes category.
Click New to create a new public attribute named Name, of data type VB::String. (You would make the type C#::string, if you wanted to code in C#.)
Click New to create a new public attribute named Address, of data type VB::String (or C#::string for C# code). Figure 5-6 shows the attributes set for the Customer class.
Figure 5-6.UML Class Properties dialog box with attributes for the Customer class
5. You can generate new code for the Customer class by selecting UML -> Code -> Generate. In the Generate dialog box (shown earlier in Figure 5-2), choose Visual Basic as the target language, check the Customer check box, specify the path as \EDWVSNETUMLMSF\Chapter 05\, and click OK. VEA then generates the code, as shown in Figure 5-7.
Figure 5-7.Generated code for the Customer class with attributes
After you’ve generated the code, you’ll notice that there are now two VB code files: Customer.vb and Customer~1.vb. The newest generated file will be Customer.vb. VEA renames existing files with a tilde and number: Customer~1.vb, Customer~2.vb, and so forth.
Stateless Classes
If a class has attributes, it must keep an instance in memory of every single class instantiation in order to save the attribute values. A class doing this is known as a stateful class. Stateless classes are better for scalability than stateful classes.
Keep in mind that when you’re developing enterprise solutions, performance and scalability are always issues. Classes should be stateless. This applies to all kinds of classes, regardless of whether they are COM+ classes, VB/C# classes, Web services, or other kinds of classes. Actually, Web services assume a stateless programming model, although state can be added to a Web service.
If your components must maintain state, the state information can be stored in a database such as SQL Server, or even in a simple text file. If your component is an ASP.NET Web service, you can store state using the built-in ASP.NET functionality for state, using the Session and Application objects.
In the old days (before .NET, that is), Microsoft encouraged developers to use the Shared Property Manager (http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/cossdk/htm/pgservices_spm_5stu.asp) to store state. The Shared Property Manager functionality is still available in .NET through the System.EnterpriseServices namespace, which provides you with important infrastructure for enterprise applications. However, the Shared Property Manager does not do well when it is accessed heavily, especially in enterprise environments with multiple processors and servers. This is simply because it doesn’t scale very well. Microsoft suggests that you do not use the Shared Property Manager for storing database connection string information, which will be accessed frequently. We recommend storing state in the database, rather than using the Shared Property Manager.
Operations
Operations are often referred to as methods. Technically, the operation is the interface, and the method is the code, but these terms are often used interchangeably. In UML class modeling, an operation can be one of the following .NET types:
Constructor
Destructor
Event3
Procedure
Property procedure
You specify the operations for the class by selecting Operations in the Categories window of the UML Class Properties dialog box (shown earlier in Figure 5-5). In Exercise 5-3, you’ll specify operations for the Customer class.
EXERCISE 5-3
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Click New to create a new public operation named GetList, of data type VB::Object (C#::object if you want to code in C#).
Click New to create a new public operation named CustomerStatus, of data type VB::Boolean (C#::bool if you want to code in C#).
Click New to create a new public operation named New. This will be our class constructor. (If you want to code in C#, the name doesn’t matter; the code generator will ignore it and name the constructor after the class.)
Click New to create a new public operation named Finalize. This will be our class destructor. (Again, If you want to code in C#, the name doesn’t matter, because the code generator will ignore it and name the destructor after the class.) Your dialog box should look like Figure 5-8.
NOTE In general, you don’t need to override the default destructor in your .NET classes. You would need to do this only when you need to perform some sort of housekeeping, such as destroying any references to classes in COM components. However, we create a class destructor in this example, just to demonstrate how it is done.
Figure 5-8. UML Class Properties dialog box with operations for the Customer class
7. Click OK.
The GetList operation is a method that provides you with a DataSet of all customers of a certain type. You specify the type via a parameter setting when calling the operation. You specify that an operation expects a parameter via the UML Operations Properties dialog box (which appears when you select the operation and click the Properties button in the Operations category of the UML Class Properties dialog box). In Exercise 5-4, you’ll specify a parameter for GetList.
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Select the GetList operation.
4. Click Properties to open the UML Operation Properties dialog box, as shown in Figure 5-9.
Figure 5-9.UML Operation Properties dialog box
5. Select the Parameters category.
6. Click New to create a new parameter customerTypeID of data type VB::Integer (C#::int if you want to code in C#). Set the Kind to in, for in-going (read-only), as shown in Figure 5-10.
Figure 5-10.UML Operation Properties dialog box with the customerType ID parameter
7. Click OK twice.
Setting the parameter Kind to in indicates that it’s an in-going (read-only) parameter. When generating code for this class, you want this operation to be a method. You can tell VEA that the code generation engine must create this operation as a procedure (method) through the Code Generation Options of the UML Operation Properties dialog box. Exercise 5-5 shows how to make sure GetList is a method.
EXERCISE 5-5
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Select the GetList operation.
Click Properties to open the UML Operation Properties dialog box (see Figure 5-9).
Select the Code Generation Options category.
Select Visual Basic (or C#, if you prefer) from the Target language list.
Select Procedure from the Kind list. Your code generation settings should look like Figure 5-11.
Figure 5-11.UML Operation Properties dialog box with code generation settings
8. Click OK twice.
9. Click the Preview code button to see what the generated code will look like, as shown in Figure 5-12. If you choose C# as the target language, the code generation preview will look like the code shown in Figure 5-13.
Figure 5-12. Code preview for the GetListoperation (VB code)
Figure 5-13.Code for the GetList operation (C# code)
The operation will be generated as a public Function4 procedure, because you’ve specified that the procedure has a return value (it will be generated as a Sub5 procedure if it doesn’t return anything).
CustomerStatus is an operation showing the customer’s online status. This operation will be a property of the class. To make sure the code generation engine generates it as a property, you can specify this in the UML Operation Properties dialog box. You’ll do this in Exercise 5-6.
EXERCISE 5-6
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Select the CustomerStatus operation.
Click Properties to open the UML Operation Properties dialog box (see Figure 5-9).
Select the Code Generation Options category.
Select Visual Basic (or C#, if you prefer) from the Target language list.
Select Property from the Kind list.
Check the Create Get Method option. Your dialog box should look like Figure 5-14.
Click OK twice.
Click the Preview code button to see the code shown in Figure 5-15.
Figure 5-14.UML Operation Properties dialog box for the CustomerStatus operation
Figure 5-15.Code Preview for the CustomerStatus operation (VB code)
In the UML Operation Properties dialog box (Figure 5-14), you can choose if you want to create Set or Get methods. A Set method allows you to set the property, and a Get method allows you to get/read the property. This particular property should be read-only, so you just need to check the Create Get Method option.
As you learned earlier, operations can also be of type constructor or destructor. A constructor is responsible for setting an object to its initial state. In other words, a constructor is a method that is called when the object is created. (Class instantiation is another term for this.) In VB, the constructor is named New. Although you can call the operation anything you like in VEA, the constructor will always be called New, once the code is generated,6 so you might as well call it New in the first place, if you’re working with VB code. As noted earlier, in C#, a constructor is named after the class. In Exercise 5-7, you’ll specify that the New operation is a constructor.
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Select the New operation.
Click Properties to open the UML Operation Properties dialog box (see Figure 5-9).
Select the Code Generation Options category.
Select Visual Basic (or C#) from the Target language list.
Select Constructor from the Kind list.
Click OK twice.
Click the Preview code button to see the code shown in Figure 5-16.
Figure 5-16.Code preview for the New constructor (VB code)
A destructor operation is called when the class is destroyed. Well, at least in theory; it might not be called, depending on how the object was disposed of. In VB, the destructor will be generated as a method with the name Finalize. If you’re working with C#, it will be named after the class. In Exercise 5-8, you’ll specify that the Finalize operation is a destructor.7
EXERCISE 5-8
Open the UML Class Properties dialog box for the Customer class by double-clicking the class.
Select the Operations category.
Select the Finalize operation.
Click Properties to open the UML Operation Properties dialog box (see Figure 5-9).
5. Select the Code Generation Options category.
6. Select Visual Basic (or C#) from the Target language list.
7. Select Destructor from the Kind list.
8. Click OK twice.
9. Click the Preview code button to see the code shown in Figure 5-17.
Figure 5-17. Code preview for the Finalize destructor (VB code)
Notice in Figure 5-17 how the Overrides keyword is automatically added to the operation signature, because the operation is a destructor. All .NET Framework classes inherit from the Object base class, which has an overridable destructor, so you must override it; you cannot shadow it.8
NOTE Please see your .NET Framework documentation for more information about the Finalize method, destructors, and garbage collection.
Listing 5-1 shows the complete code generated for the Customer class. To generate the code, select UML -> Code -> Generate. In the Generate dialog box (shown earlier in Figure 5-2), choose Visual Basic as the target language, select the Customer option, specify the path as \EDWVSNETUMLMSF\Chapter 05\, and click OK.
Listing 5-1. Code Generated for the Customer Class
1 Namespace MyNamespace 2 3 Public Class Customer 4 5 Public Name As String 6 7 Public Address As String 8 9 Public Function GetList (ByVal customerTypeID As Integer) As Object 10 11 End Function 12 13 Public ReadOnly Property CustomerStatus () As Boolean 14 Get 15 16 End Get 17 18 End Property 19 20 Public Sub New () 21 22 End Sub 23 24 Protected Overrides Sub Finalize () 25 26 End Sub 27 28 End Class 29 30 End Namespace
Now that you’ve seen how the packages and classes elements of class diagrams work, we’ll look at the final element of these diagrams: relationships.
The classes on your class diagrams can have three basic kinds of relationships:
Binary association
Dependency
Generalization
These relationships are discussed in the following sections.
Binary Association
The binary association is the most common class relationship. It simply specifies that (exactly) two classes are associated, meaning that they interact or are related in some way. You usually create a binary association relationship between two classes when an instance of one class eventually will communicate with an instance of the other class.
For example, a Customer class can have a binary association relationship to a ShoppingBasket class, to indicate that an object of type Customer communicates with one of type ShoppingBasket class. Your binary associations can also specify the way that the classes will be implemented. If you double-click a binary association, you’ll see the UML Association Properties dialog box, where you can specify the properties for the association, determining not only the business semantics of the association, but also the design decisions about the way the association is implemented.
In Exercise 5-9, you’ll add a ShoppingBasket class to your class diagram and give it a binary association relationship to the Customer class.
EXERCISE 5-9
Open the MyClassDiagram static structure diagram.
Add a new class named ShoppingBasket to the diagram.
Add a binary association between the Customer and ShoppingBasket classes.
Right-click the binary association and select Shape Display Options from the pop-up menu to open the UML Shape Display Options dialog box.
Select the Name option. Deselect the First end name option and the Second end name option. Then click OK.
Double-click the binary association to open the UML Association Properties dialog box.
Type Buying in the Name field.
Select forward from the Name Reading Direction list.
In the Association Ends section, select the End1 association end and change its name to Customer. Change its multiplicity to 1.
Select the End2 association end and change its name to ShoppingBasket. Change its multiplicity to 0..*. Select the IsNavigable property. Your UML Association Properties dialog box should look like Figure 5-18.
Figure 5-18.UML Association Properties dialog box
11. Click OK.
Figure 5-19 shows the binary association between the Customer and ShoppingBasket classes. The association is named Buying. The association ends indicate how the association will be implemented. By specifying that the ShoppingBasket association end is navigable (IsNavigable is selected), you indicate that the Customer class is able to navigate to the ShoppingBasket class. Furthermore, VEA adds an arrowhead to the association end. The Customer association end isn’t navigable, which means that the ShoppingBasket class doesn’t have navigation access to the related Customer class. The multiplicity of 1 for Customer means that each instance of ShoppingBasket relates to only one Customer. The multiplicity of 0..* for ShoppingBasket means that each Customer can have zero or many ShoppingBasket instances.
Figure 5-19.Binary association between the Customer and ShoppingBasket classes
If you double-click the Customer class, select the Code Generation Options category in the UML Class Properties dialog box, and click the Preview code button, your generated code will look like Listing 5-2. Remember that you need to connect the association ends to the two classes to generate the code shown in Listing 5-2.
Listing 5-2. Code Generated for the Customer Class with a Binary Association
1 Imports TopPackage.MyNamespace 2 Namespace MyNamespace 3 4 Public Class Customer 5 6 Public Name As String 7 8 Public Address As String 9 10 Private ShoppingBasket As System.Collections.ArrayList 11 12 Public Function GetList (ByVal customerTypeID As Integer) As Object 13 14 End Function 15 16 Public ReadOnly Property CustomerStatus () As Boolean 17 Get 18 19 End Get 20 21 End Property 22 23 Public Sub New () 24 25 End Sub 26 27 Protected Overrides Sub Finalize () 28 29 End Sub 30 31 End Class ' END CLASS DEFINITION Customer 32 33 End Namespace ' MyNamespace
You can see that the binary association is implemented through line 10, which is an array of objects (the instantiated ShoppingBasket classes). If you set the multiplicity of ShoppingBasket to 1, your code will be generated like this instead:
Private ShoppingBasket As MyNamespace.ShoppingBasket
Dependency Relationship
The dependency relationship specifies that one class has a build dependency on another class, but does not maintain a permanent link to an object of that class. Changes to a class will affect all classes that are dependent on that specific class. Usually, the dependency relationship indicates that the dependent class is invoking at least one operation on the class on which it depends. The dependency relationship doesn’t have any impact on the code generation, although it features in the project references.
In Exercise 5-10, you’ll add two classes, named VIPCustomer and Bonus, to your class diagram and create a dependency relationship between those two classes.
EXERCISE 5-10
In the MyClassDiagram static structure diagram, add a new class named VIPCustomer.
Add a new class named Bonus to the diagram.
Add a dependency relationship between the VIPCustomer and Bonus classes.
In Figure 5-20, you can see that the class VIPCustomer is dependent on the Bonus class. VIPCustomer would be dependent on Bonus, if, for example, Bonus provides access to some customer bonus points that must be present for the VIPCustomer class to function properly. Having the dependency relationship between the VIPCustomer and Bonus classes tells the developers that any changes made to the Bonus class might affect the VIPCustomer class. This fact must be taken into consideration when deciding on whether to implement the change and how it should be tested.
Figure 5-20.Dependency relationship between the VIPCustomer and the Bonus classes
The generalization relationship specifies that a class is a subclass of another class and inherits the base class’s public operations and attributes. For example, if you have a base class and you want all other classes to inherit from that class, you’ll place a generalization relationship between the two classes. The same applies if your classes represent ASP.NET pages and you want all ASP.NET pages to inherit from a base ASP.NET Web page.
In Exercise 5-10, you added the VIPCustomer class to your class diagram. This class represents important customers who might be involved in a bonus program. Still, VIP customers are indeed customers as well, so inheritance from the Customer base class makes sense. In Exercise 5-11, you’ll add a generalization relationship between the Customer and VIPCustomer classes.
EXERCISE 5-11
In the MyClassDiagram static structure diagram, add a generalization relationship between the Customer and VIPCustomer classes.
Double-click the generalization relationship to open the UML Generalization Properties dialog box.
Select inherits from the Stereotype list, to show this stereotype on the diagram.
Click OK.
In Figure 5-21, you can see the VIPCustomer inherits the Customer through the generalization class, and thereby gets access to the operations and attributes from the Customer class.
In Figure 5-22, you can see the code generation preview for the VIPCustomer class. Notice that the class inherits from Customer and that it imports the MyNamespace namespace, because Customer is created within this namespace.
NOTE For code-generation purposes, you don’t need to specify the inherits stereotype. The correct code will be generated as long as you specify the appropriate generalization relationship.
Figure 5-21.Generalization relationship between the Customer and VIPCustomer classes
Figure 5-22.Code preview for the VIPCustomer class
Generalization, OOP Inheritance, and Polymorphism
In OOP, inheritance is modeled via generalization. You can create multiple inheritances (nested). However, too many levels of inheritance make your code and model difficult to read and maintain. You should avoid nesting inheritance more than three levels deep.
You can inherit a class you don’t even have the source code for and use it in your own class. Can you see the advantage? Find a class that almost does what you want. Create your own class that inherits from the one you’ve found, and add the functionality it’s missing. This is the essence of OOP.
Polymorphism is tightly related to inheritance. Polymorphism means that an operation can exist in several versions. For example, suppose that you have a class named SoccerPlayer with an operation named Run. The Run operation tells the soccer player where to run. If the player is a defender, he will run to a different location on the field than a midfielder will run. So, add two more classes called Defender and Midfielder, both inheriting from the SoccerPlayer class. Wouldn’t it be nice if both classes have an operation called Run that overrides the Run operation from the SoccerPlayer class? Yes, it would, because that way, you only need to call the Run operation in your program, and your player will always run to the right location. This is polymorphism, because your Run operation exists in different versions and overrides the parent Run operation.
To implement polymorphism, simply add an operation with the same name in both the parent class and the child class, and mark the operation in the parent class as IsPolymorphic (from the UML Class Properties dialog box or the UML Operation Properties dialog box). The code generator adds the Overridable keyword to the operation in VB; no keyword is necessary in C#.
Statechart Diagrams
A statechart diagram is created to identify and document the internal behavior of an object. This way, you can get an overview of the object life cycle and identify the functionality to be implemented. Statechart diagrams are popular when developing truly object-oriented software, because they describe the dynamic behavior of an object in detail.
The content of a statechart diagram is a sequence of states that the object goes through during its lifetime. Legal state changes are shown by transitions. Events cause the object to change from one state into another. Statechart diagrams consist of the following:
States
Transitions
Decisions
Figure 5-23 shows a statechart diagram with these elements. The following sections describe how they work.
States are “situations” an object can be in. For example, if you have an Invoice object, it can be in the “Due for payment” state, if it has reached the due date for payment. If the customer pays the invoice, the Invoice object transitions to the “Paid” state, which means it can no longer be opened for editing.
A statechart diagram must consist of one initial state and may have one or more final states. In Exercise 5-12, you’ll create a statechart diagram and add states to it.
EXERCISE 5-12
Open VEA, if is not already open.
Create a statechart diagram (static structure diagram) named MyStatechartDiagram.
Drag an Initial State shape onto MyStatechartDiagram.
Drag a State shape onto MyStatechartDiagram and double-click the shape to open the UML State Properties dialog box.
Type Due for payment in the Name field and click OK.
Add two more State shapes and name them Paid and Unpaid.
Drag a Decision shape onto MyStatechartDiagram.
In Figure 5-23, you can see an Initial State shape, which is the starting point, illustrated by a solid dot, and a Final State shape, illustrated by a solid dot surrounded by a circle (colloquially called the fried egg). In between are the states the Invoice object goes through: Paid and Unpaid.
Transitions
You create transitions to specify how the object transitions from one state into another. Actually, a transition represents an event. Something happens (triggers) the object to transition into a new state. You may attach more than one transition from a state, if the transitions are unique and the triggering events are different. In Exercise 5-13, you’ll add transitions to your statechart diagram.
EXERCISE 5-13
On MyStatechartDiagram, create a transition between the initial state and the Due for payment state.
Create a transition between the Due for payment state and the Decision shape.
Create a transition between the Decision shape and the Paid state.
Create a transition between the Decision shape and the Unpaid state.
Create a transition between the Paid state and the final state.
Create a transition between the Unpaid state and the final state.
In Figure 5-23, you can see transitions, illustrated by solid lines with arrowheads, pointing to the new states. Transitions can also contain guard expressions, as explained in the next section.
Decisions
Decisions are created to illustrate that a transition can end in one of several possible states, depending on a decision. When creating a decision, you must add guard expressions to the transitions going out from the decision; otherwise, you would not be able to tell from the diagram which rule decides which state the transition leads to. Guard expressions specify a condition for the transition; that is, when a transition can occur. In Exercise 5-14, you’ll add decisions to your statechart diagram.
EXERCISE 5-14
On MyStatechartDiagram, double-click the transition between the Decision shape and the Paid state to open the UML Transition Properties dialog box.
Select Pseudocode from the Language list and select the Guard option.
Type If payment is registered in the Body text field. The UML Transition Properties dialog box should look like Figure 5-24.
Figure 5-24.Guard expression for outgoing transition
4. Click OK.
5. Double-click transition between the Decision shape and the Unpaid state to open the UML Transition Properties dialog box.
6. Select Pseudocode from the Language list and select the Guard option.
7. Type If payment is not registered in the Body text field.
8. Click OK. The MyStatechartDiagram should now look like the one shown in Figure 5-23.
The Decision shape is a diamond. In Figure 5-23, you can see a Decision shape indicating that the transition from the Due for payment state can transform into two different states, depending on the payment status.
Component Diagrams
In the old days (before .NET, that is), components always referred to COM+ components in a Microsoft enterprise environment. These components could be implemented as .exe or .dll files. When developing .NET applications, you can still create COM+ components (in fact, you’ll often need to use existing COM+ components when developing enterprise applications), but you’ll also develop classes in .NET and Web services.
In .NET, the classes and Web services (.NET components) are not deployed in .dll files as COM+ components are. Instead they are deployed in assemblies, which are the smallest possible unit of deployment in .NET, and, in fact, they are often .dll files. Is this confusing? Well, just think of assemblies as physical files.9 Assemblies contain the metadata in a data structure called manifests. This means that .NET components are self-describing and don’t need to be registered on the server; you simply copy the assemblies to the server. This is often referred to as the .NET way to eliminate the infamous DLL hell, by the way.10
NOTE Shared assemblies still need to be “installed” in the Global Assembly Cache (GAC). See your .NET Framework documentation for more information.
The purpose of creating component diagrams is to show how you’ll implement your components (which contain classes). Components consist of one or more classes, which, in turn, can be instantiated in your application.
Let’s say you have 20 classes in your application (diagrammed in your class diagram, a static structure diagram in VEA). Before physically implementing your classes, you’ll need to decide which classes will be implemented in which physical files. That’s exactly what you do in the component diagrams. So, in fact, generating code doesn’t make much sense unless you’ve decided in which components you want to implement your classes; otherwise, you don’t know which physical files to generate.11
When developing .NET solutions, your component diagrams will represent COM+ .dll files and assemblies (which are often .dll files as well). Component diagrams consist of the following:
Components
Nodes
Dependencies
Interfaces
A component diagram may represent all the components or just some of the components in a system. Figure 5-25 shows an example of a component diagram.
The key element of component diagrams is the component element. A component element represents some kind of software. It can represent a code module, an executable, a DLL, and so on. The following are the key properties of a component:
Name: Typically, the physical name of the file (such as customer.dll)
Stereotype: Specifies the subtype of the component, for example, EXE or DLL
Nodes: Specifies on which node (server) the component will be implemented
Classes: Specifies which classes make up your component
These properties are all accessible from the UML Component Properties dialog box, which opens when you double-click a component on a diagram.
In Exercise 5-15, you’ll create a component diagram and add three component elements.
EXERCISE 5-15
Open VEA, if isn’t already open.
Create a component diagram named MyComponentDiagram.
Add a component named Component1 to the diagram.
Add a component named Component2 to the diagram.
Add a component named Imageshrink.dll to the diagram.
You can see the notation of each component in Figure 5-25.
Nodes
The node element identifies a physical, runtime object representing a processing resource. A node will typically be a server on which your components are processed or to which they are related. The following are some of the key node properties:
Chapter 5
Name: The name of the node
Location: The node location
Components: Specifies which components are related to the node
In Exercise 5-16, you’ll add a node to your component diagram, and then specify which component will execute on this node.
EXERCISE 5-16
On MyComponentDiagram, add a node named Node1.
Double-click Node1 to open the UML Node Properties dialog box.
Select the Components category.
Select Imageshrink.dll, as shown in Figure 5-26.
Figure 5-26.Components category of the UML Node Properties dialog box for Node1
5. Click OK.
You can see the notation of the node in Figure 5-25. Because you set the Components property for Node1 to the Imageshrink.dll component, the Imageshrink.dll file will execute on this server/node.
Dependency is a relationship indicating that one element is dependent on another element to function. Say Component1 is dependent on Component2, which means that changes in Component2 will affect Component1, because Component1 uses services or facilities in Component2, and not the other way around.
For example, you might have a component in which some of the classes call a class in another component. Well, why not package all classes in the same component then?12 It defeats the idea of creating unique components that hold only classes that can be grouped because they expose the same kind of functionality. It’s like when you’re at the grocery store; you don’t look for apples in the basket with oranges, do you?
Establishing dependencies has another benefit in that if your component crashes, it will directly affect only one type of functionality. It shouldn’t bring other types of functionality down with it.
In Exercise 5-17, you’ll add a dependency between two components in your component diagram.
EXERCISE 5-17
1. Open MyComponentDiagram.
2. Add a dependency between Component1 and Component2.
In Figure 5-25, you can see that Component1 is dependent on Component2, illustrated by the dependency (dashed line).
Interfaces
Of course, you know about your model’s public interfaces by looking at your public functions in the classes. However, if you have a component representing a third-party DLL, you might not know about the public interfaces, because you haven’t modeled the classes and functions. That’s when the interface element of a component diagram can be useful.
Like any class, an interface needs at least one operation, and operations can have parameters. In Figure 5-25, the Imageshrink.dll component represents a COM+ component purchased from an external vendor. The _Imageshrink interface is the COM+ type library. A type library is required to expose the interfaces to users of the component. The interfaces exposed are illustrated as operations on the Interface shape. In Exercise 5-18, you’ll add an interface that has one public operation, Shrink, to your component diagram. This operation will represent a function and have three parameters.
EXERCISE 5-18
On MyComponentDiagram, add an interface named _Imageshrink to the Imageshrink.dll component. The Interface shape is the lollipop, as shown in Figure 5-25.
Double-click the _Imageshrink interface to open the UML Interface Properties dialog box.
Select the Operations category.
Click New to add a new operation named Shrink, of data type VB::Object (C#::object if you want to code in C#), as shown in Figure 5-27.
Figure 5-27.Operations category of the UML Interface Properties dialog box
5. Click OK.
NOTE You can’t tell from the Model Explorer whether the operation returns a value. You need to open the UML Operation Properties dialog box to get that information.
6. Double-click the _Imageshrink interface to open the UML Interface Properties dialog box and select the Operations category again.
7. Select the Shrink operation.
8. Click Properties to open the UML Operation Properties dialog box.
9. Select the Parameters category.
10. Click New to add a new in-going parameter named BMPImage of data type VB::Object (or C#::object if you want to code in C#).
11. Click New to add a new in-going parameter named ShrinkPercentage of data type VB::Integer (or C#::int if you want to code in C#). Your UML Operation Properties dialog box should look like Figure 5-28.
Figure 5-28.Parameters category of the UML Operation Properties dialog box
12. Click OK twice. In Figure 5-29, you can see how the _Imageshrink interface looks in the Model Explorer.
Figure 5-29. Interface shown in the Model Explorer
NOTE As you can see in the Model Explorer, the Shrink interface has three parameters: Shrink, BMPImage, and ShrinkPercent. What you can’t tell from the Model Explorer is if a parameter is an input or output parameter. You need to open the UML Operation Properties dialog box to see this information.
When developing COM+ components to run in the COM+ runtime environment of Component Services, you need to instantiate the ObjectControl interface of the COM+ runtime environment and add three operations to your component: Activate, Deactivate, and CanBePooled. In Figure 5-30, you can see how the ObjectControl interface could be added to the Imageshrink.dll component in our example. You’ll add the ObjectControl interface in Exercise 5-19, in the next section.
Figure 5-30.COM+ component with the ObjectControl interface
If you generate code from your component diagram alone, you can generate code for only the interfaces, not the components. This is very logical if you think about it, because the components on your component diagram just represent the physical file containing the classes and interfaces.
To generate code for your interfaces, select UML -> Code -> Generate. You can choose which model elements to generate code for from the Generate dialog box. Figure 5-31 shows the Generate dialog box set for generating code for the _Imageshrink interface to the \EDWVSNETUMLMSF\Chapter 05\ path.
After clicking OK in the Generate dialog box, you can open the code file in VS .NET (or any text editor, like Notepad, for that matter) from the path you’ve just specified. Figure 5-32 shows the _Imageshrink.vb file generated for the _Imageshrink interface using the settings in Figure 5-31.
Figure 5-31.Generate dialog box with the _ImageShrink interface selected
Figure 5-32.VB code generated for the _Imageshrink interface
However, generating code for interfaces alone doesn’t make much sense if your interface is connected to one of your own classes. It’s more relevant to generate code for the class that incorporates the interface. In Exercise 5-19, you’ll create a new class diagram with the Class1 class and incorporate the _ImageShrink and ObjectControl interfaces described in the previous section. Then you’ll generate the code for that class.
EXERCISE 5-19
1. In VEA, create a class diagram named ClassInterfaceDiagram.
2. Add a class named Class1 to the diagram.
3. Add the _Imageshrink interface to the diagram, attaching it to Class1. You can copy the interface from the MyComponentDiagram diagram you created in the previous exercises.
4. Add an interface named ObjectControl to Class1.
5. Double-click the ObjectControl interface to open the UML Interface Properties dialog box, and then select the Operations category.
6. Add three operations to the ObjectControl interface named Activate, Deactivate, and CanBePooled. (See Exercise 5- 18 for instructions on how to add the operations.) Your diagram should now look like Figure 5-33. Figure 5-33.Class diagram with Class1 class incorporating the ObjectControl and _imageshrink interfaces
7. Double-click Class1 to see its properties, as shown in Figure 5- 34. As you can see, Class1 doesn’t have any operations. All the operations shown in Figure 5-33 are implemented from the interfaces. Click Cancel to close the dialog box.
8. Select UML -> Code -> Generate. In the Generate dialog box, select only Class1 in the tree view, and then click OK. Your generated code will look like the code in Figure 5-35.
Figure 5-34.UML Class Properties dialog box for Class1, showing the class does not have any operations
Figure 5-35.Code generated for class1 including interfaces
as you can see, the code listed in Figure 5-35 makes more sense than the code listed in Figure 5-32. The code in Figure 5-35 shows how the class implements the interfaces and that the Shrink operation is automatically generated.
Deployment Diagrams
The component diagram shows how the components are physically grouped and implemented. A deployment diagram takes this a step further and shows how the hardware and software elements of your solution will be configured and implemented. You can see how your components will be implemented in the runtime environment. As a rule of thumb, each UML model should contain only a single deployment diagram showing how the solution will look like from a runtime point of view.
A deployment diagram basically consists of three elements:
Nodes
Components
Relationships
Figure 5-36 shows elements in a deployment diagram, as discussed in the following sections.
Figure 5-36.Deployment diagram with two different node types
Nodes in deployment diagrams represent hardware components capable of executing software code. The notation of a node is a shaded, three-dimensional box. You can choose between two types of node elements:
The node element, which represents a specific type of node, for example, a server
The node instance element, which represents an instance of a node type
In Exercise 5-20, you’ll create a deployment diagram and add a node element and a node instance element.
EXERCISE 5-20
Open VEA, if it is not already open.
Create a deployment diagram named MyDeploymentDiagram.
Add a node named Server to the diagram.
Add a node instance named Web Server Alpha to the diagram. Your deployment diagram should look like Figure 5-36.
As you can see in Figure 5-36, the node named Web Server Alpha is a node instance element and, as such, an instance of the Server node.
NOTE In the Model Explorer, you can see only node elements, not node instance elements. This is because node instance elements are simply instances of other node elements.
Components
You can add components to your deployment diagram to specify on which servers the components will be physically running. As is the case with nodes, there are two kinds of components:
The component element represents a physical component.
The component instance element represents a runtime instance of a physical component.
You can document on which server (node) the components will be running by applying them to the specific node.
In Exercise 5-21, you’ll add two components to your deployment diagram: Customer and Discount (see Figure 5-35).
EXERCISE 5-21
On MyDeploymentDiagram, add a component named Customer.
Add a component named Discount to the diagram.
Double-click the Customer component to open the UML Component Properties dialog box.
Select the Nodes category.
Select the Server node. The UML Component Properties dialog box should now look like Figure 5-37.
Figure 5-37.UML Component Properties dialog box
6. Click OK.
In the exercise, you used the UML Component Properties dialog box to specify on which node the Customer component would run. Another way to do this is through the UML Node Properties dialog box. Double-click the node on which the component is running and select the Components category, as shown in Figure 5-38.
Figure 5-38.UML Node Properties dialog box
Relationships
Deployment diagrams have two basic relationship types:
Communicates: The communicates relationship is for specifying how two elements communicate with each other. In Figure 5-36, you can see a communicates relationship between the two node elements. This indicates that the two nodes are communicating with each other. Actually, when placing a communicates relationship between two nodes (which represent hardware components, remember?), it usually means that the two nodes are connected in some way. That’s why we’ve made the example of a server connected to an overhead projector (for example, using USB). Figure 5-36 also shows a communicates relationship between the Server component and the Customer component, illustrating that the component is installed on the server and is processed there.
Dependency: The dependency relationship specifies a dependency between two elements. In Figure 5-36, the Customer component is dependent on the Discount component, and therefore affected by its availability or by changes to its functionality. You can use the dependency relationship freely to specify important issues, helpful for your implementation. For example, in Figure 5-36, we’ve added a dependency relationship between the Server node and the Web Server Alpha node instance to indicate that the server implementation will take place on the server named Web Server Alpha.
In Exercise 5-22, you’ll add relationships to your deployment diagram.
EXERCISE 5-22
On MyDeploymentDiagram, add a communicates relationship between the Server and Overhead Projector nodes.
Right-click the relationship and select Shape Display Options from the pop-up menu to show the Shape Display Options dialog box.
Deselect the following options: First end name, Second end name, and End multiplicities. Then click OK.
Add a communicates relationship between the Server node and the Customer component.
Right-click the relationship and select Shape Display Options from the pop-up menu to show the Shape Display Options dialog box.
Deselect the First end name, Second end name, and End multiplicities options. Then click OK.
Add a dependency relationship between the Server node and Web Server Alpha node instance.
Add a dependency relationship between the Customer and Discount components. MyDeploymentDiagram should now look like Figure 5-36.
We have mentioned stereotypes several times in this chapter and previous chapters. Stereotypes are simply a subtype you specify for a type.
For example, a use case is of the type Use Case. If you then specify the stereotype to be Business Use Case, the use case is still a use case, but it’s a use case of the type Business Use Case. Similarly, you can specify that a class is a Class but also a class of the type struct.
Stereotypes are just a way for you to specify subtypes for your types. The notation for a stereotype is <<stereotype name>>. Stereotypes exist for almost all elements. You can apply the built-in stereotypes provided by VEA or define your own stereotypes, as described in the following sections.
Built-in Stereotypes
The built-in stereotypes are predefined by VEA and vary from element type to element type. For example, the struct stereotype is one of the stereotypes available for classes in the UML Class Properties dialog box, as shown in Figure 5-39.
Figure 5-39.Stereotypes in the UML Class Properties dialog box
Figure 5-40 shows the Class1 class with the struct stereotype.
Figure 5-40.Class1 class with the struct stereotype
User-Defined Stereotypes
Creating your own stereotypes is useful if you want to specify a stereotype for a commonly used element that is not predefined by VEA. For example, you might want to specify that a class will be implemented as an ASP.NET page. VEA doesn’t have a built-in stereotype to represent an ASP.NET page, but you can easily create one. The only disadvantage of using user-defined stereotypes is that, because VEA doesn’t know about the user-defined stereotypes, they can’t be reflected in the generated code. In other words, it’s a visual thing only.
To create a new stereotype, select UML -> Stereotypes to open the UML Stereotypes dialog box, as shown in Figure 5-41. Then click New. Type the stereotype name in the stereotype field of the new stereotype row, which is created at the top of the Stereotypes list. The important thing to remember is to choose to which base class the new stereotype belongs from the Base Class list; otherwise, it will not be accessible from the Stereotype list for the element.
Figure 5-41.UML Stereotypes dialog box
In Figure 5-42, you can see that the Class1 class has the ASP.NET Page stereotype.
Figure 5-42. Class1 class with the ASP.NET Page stereotype
UML Code Mapping to .NET
In this chapter, you’ve learned that some of the UML elements can be mapped directly to the .NET language. In Table 5-1, you can see how some of the UML elements from the structural models map to the .NET language. You’ll need to know about code mapping if you want to generate code from your UML diagrams.
Table 5-1. UML Code Mapping to .NET
UML Element
VB .NET Code
C# Code
Package
Namespace keyword
namespace keyword
Class
Class keyword
class keyword
Procedure operation without return parameter (class)
Sub keyword
Prefix procedure name with void keyword
Procedure operation with return parameter (class)
Function keyword
This is the default operation, so no keyword or prefix is necessary
Destructor operation (class)
Sub Finalize procedure
No keyword is necessary; it has the same name as the class, prefixed with a ~character
Constructor operation (class)
Sub New procedure
No keyword is necessary; it has the same name as the class
Event operation (class)
Event keyword
event keyword
Property operation (class)
Property keyword
property keyword
Generalization relation (class)
Inherits
No keyword is necessary; simply add a colon after the class name and then the name of the base class
Attribute (class)
Declare as variable
Declare as variable
Binary association (with bounded multiplicity and IsNavigable selected)
Data member (variable)
Binary association (with unbounded multiplicity and IsNavigable selected)
In Chapter 4, you learned about the behavior diagrams, and you saw how the behavior diagrams serve two purposes: they give you an overview of the solution you’re planning, and they enable you to create the structural diagrams, because they provide you with information about what happens in your solution.
In this chapter, you’ve learned about the structural UML models, which describe the static structure of your solution. You’ve learned how to generate code for your solution by creating the static structure diagram–—the class diagram. You’ve also seen how the structural diagrams directly build on the information you’ve gathered for your solution during the creation of the behavioral diagrams. Finally, you’ve seen how the UML elements map to .NET code and declarations.
You’ve now learned about the UML diagrams in detail. In the next chapter, you’ll learn how to plan an enterprise solution with the knowledge you’ve just obtained.
Footnotes:
1. Static structure diagrams can be class diagrams or another diagram type covering the static structure (for example, an object diagram showing physical real-life objects).
2. This is true for all UML CASE tools and not just a disadvantage for VEA users.
3. In VEA, events are operations, because of UML 1.2 compliance. However, this isn’t the case in UML 1.4 and later.
4. Methods are functions by default in C#.
5. In C#, this is a method with the void keyword in the signature to indicate that it doesn’t return a value.
6. Well, your code wouldn’t work as intended if not, would it?
7. With garbage collection, the Finalize method isn’t really a destructor in the true sense, but we’ll stay with the OOP terminology.
8. Shadowing means that the operation in the base class with the same name as an operation in the derived class is hidden, but still accessible. A shadowed operation can be called directly from the caller of the derived class, unlike an overridden operation, which can be called only on the derived class.
9. .NET assemblies can consist of one or more physical files, or even be in dynamic assemblies that are contained in memory. However, for the sake of this discussion, an assembly equals one physical file.
10. “DLL hell” refers to the annoying struggle of registering and unregistering the different versions of .dll files in the Registry, often resulting in a DLL version incompatible with the application being registered. .NET solves this by making the assemblies (DLLs) selfdescribing through the use of a manifest, which means that registration is no longer necessary, and allows the use of side- by-side assemblies, or private assemblies.
11. VEA allows you to generate code without assigning your classes to components. You simply choose the physical file names when generating code.
12. To some extent, this will prevent you from distributing your classes to different physical machines. This isn’t entirely true, however, because the same component can be running different machines at the same time.