Now that we've demonstrated how to visualize existing code using a Class Diagram, we'll now take a step back and look at this from another angle — how to design your code types from scratch using Class Designer, before undertaking the hardcore programming using the code editor.
There is no benefit in attempting to design some types up-front using Class Designer, because it's more effective to let Visual Studio generate them automatically. Specifically, we're referring to the application classes that are generated when you implement an application directly from Application Designer, or the classes that you get when you create a new project from a template. In addition, the UI classes for forms, buttons, and so on are better designed using the web/windows forms designers — to be visualized subsequently using Class Designer as we did in Figure 5-2.
Some notations and processes, particularly the UML/RUP combination, encourage a "user experience modeling" activity that involves defining screens, forms, and fields in an abstract sense before coding them in any language. This may have some limited benefit during the analysis phase, but serves no useful purpose during design — specifically, .NET design — where more suitable tools are provided.
So what kinds of classes should be designed initially using Class Designer, rather than in code? Consider the three classes StockDeal, StockPurchase, and StockSales, that we visualized in Figure 5-3. These classes could not be generated automatically by Visual Studio, nor could they be designed using one of the other designers such as the forms designer. Those classes — which we'll refer to as domain classes — are conceived mentally, or in a team brainstorming session, according to a narrative like the following:
"We need to represent two kinds of deals: purchases and sales. These have common attributes, such as the number of shares and the stock symbol for the deal, which could be recorded in a base class. That base class would also facilitate a DealHistory, which would list previous deals regardless of whether they are sales or purchases. Obviously, there would be no StockDeal that was neither a purchase nor a sale, so the base class would be abstract."
While working through that narrative, you might have drawn a mental picture of how these classes would fit together; and in the brainstorming scenario, you may well have drawn up that mental picture informally with lines and boxes. Using Class Designer enables you to formalize the mental picture in a way that is accessible to noncoders as well as coders, and without having to worry about the exact syntax for defining the classes in code using various languages.
Additional examples of using Class Designer to model a set of domain classes in support of an API can be found in Tony Loton's MSDN article "Designing an API with the Visual Studio 2005 Class Designer" at http://www.msdn.microsoft.com/library/default.asp?url=/library/enus/dnvs05/html/vstsclassdesigner.asp.
To begin the process of designing the StockDeal classes using Class Designer you need to do the following:
Create a new Visual Basic Class Library project in Visual Studio, and add a Class Diagram to it named StockDeal.cd.
Drag an Abstract Class type from the toolbox to the design surface and name it StockDeal. A dialog will force you to name the class when placing it on the design surface.
Drag a Class type from the toolbox to the design surface and name it StockPurchase.
Drag a Class type from the toolbox to the design surface and name it StockSale.
Note that wherever the steps say "drag a class type from the toolbox" you can also use the context menu of the diagram (right-click an empty portion) to the same effect. However, to add relationships (see the following section) you have to use the toolbox.
To show that the StockPurchase and StockSale classes derive from the StockDeal base class, you need to draw in two inheritance relationships. Click the Inheritance item in the toolbox, click the StockPurchase class, and drag the connection to the StockDeal class. Then do the same for the inheritance between StockSale and StockDeal.
Your diagram should be starting to look like Figure 5-3, at least in outline. Already you have modeled the essential relationships, which have been reflected automatically in the code as follows:
Public MustInherit Class StockDeal Public Class StockSale Inherits ClassLibrary.StockDeal Public Class StockPurchase Inherits ClassLibrary.StockDeal
The process for drawing inheritance relationships using Class Designer is the same regardless of which programming language you're using, so you don't need to remember whether the syntax is "Must Inherit" (Visual Basic) or "abstract" (Visual C#).
We have demonstrated how to draw an inheritance relationship between two classes. You can also draw inheritance relationships between two interfaces, or between a class and an interface. In the latter case, the interface will be visualized independently on the design surface and as a lollipop shape emanating from the class shape. It will also auto-generate stubs that implement the interface.
Now we need to populate those classes with members.
To add new members to a type, you can right-click the shape on the diagram and choose Add from the pop-up menu. You will be able to add a Method, Property, Field, Event, Constructor, Destructor, or Constant. Attempting to add a member will display the Class Details window.
Note that you cannot add members in this way to types that are referenced in other assemblies, such as the Button class that was visualized on the Class Diagram in Figure 5-2.
The Class Details window is rather like the Web Service Details window that you met in Chapter 2. When you click a type on the diagram, this window shows the following information for every member of the type: the member name, its type, its modifier, and any comments you have entered. You can edit any of those things providing the member is not read-only; and in the case of member types, you can take advantage of the full Intellisense support. You can also add new members by clicking the <add property>, <add method>, <add field>, or <add event> indicators as appropriate.
Method and Property members may be expanded to reveal additional indicators for <add parameter>.
The following table gives an example entry for a StockSymbol property as it would appear in the Class Details window.
Unique identifier for the stock
There is one further column, Hide, which we have not shown. This additional column has checkboxes that enable you to selectively hide members on the diagram.
Use the Class Details window to populate the StockDeal, StockPurchase, and StockSale classes with their members, in order to reflect the diagram shown in Figure 5-3 and the final code listed earlier. Pay particular attention to the NumberOfShares property that is present in all three classes.
You may remember that in the "From Code to Class Diagrams" section we coded that property in the abstract base class (StockDeal) as follows:
Public MustOverride ReadOnly Property NumberOfShares() As Integer
In the StockSale derived class we defined it this way:
Public Overrides ReadOnly Property NumberOfShares() As Integer Get Return NumberOfSharesSold End Get End Property
The definition in the StockPurchase derived class was the same except for the Return statement, which was Return NumberOfShares Bought.
When working in the opposite direction, from class diagrams to code, you can specify such overriding of members using Class Designer as follows:
Once you've added the member (in this case, the NumberOfShares property) to the base class, change the Inheritance Modify setting in the Properties window to MustOverride.
Right-click the derived class (StockSale or StockPurchase) and choose Intellisense Override Members from the context menu.
Select the member to override, which in this case is NumberOfShares.
Having looked at the inheritance relationship, we'll now turn our attention to the other kind of relationship that you can draw between classes — namely, association. An association shows that a class holds a reference to another class such that instances of the two classes are linked via a member variable.
If you have a UML background, you may be familiar with the stronger forms of association called aggregation and composition, which are not supported in Class Designer. This is good news in that, when implemented, aggregation and composition are usually indistinguishable because most programming languages — apart from C++ — do not distinguish between "by reference" members (association) and "by value" members (aggregation/composition). Furthermore, the stronger forms of association are defined imprecisely, leading to debate and misinterpretation even among the experts.
As a practical example of association, we'll introduce a new class named Customer that will hold a reference to the last deal placed by the customer:
Add a new Class Diagram named CustomerStockDealClasses.cd.
Drag a new class from the toolbox onto the design surface, and name that class Customer.
Drag the StockDeal class from Solution Explorer onto the design surface.
Choose Association from the toolbox, click the Customer, and drag to StockDeal.
In Properties — or on the diagram — rename it lastDeal.
The result should be as shown in Figure 5-4, which — among other things — demonstrates that the same class (in this case, StockDeal) may be shown on more than one class diagram.
Placing a class on more than one diagram does not create a copy of the class.
Naming the association lastDeal causes a property of that name to be added as a member to the Customer class, as shown here:
Public Class Customer Public Property lastDeal() As ClassLibrary.StockDeal Get End Get Set(ByVal value As ClassLibrary.StockDeal) End Set End Property End Class
That property is the realization of the association in code.
If you think about it, this is just a property like any other, so there's no logical reason why it could not be represented simply as a member of the Customer class, with no association line at all. Indeed, that is the case, and you can choose that alternative representation by right-clicking the association line and selecting Show as Property from the pop-up menu. You can also reverse that action at any time by right-clicking the lastDeal property member and selecting Show as Association. You can even multiple-select properties or fields prior to invoking the Show As Association feature.
UML tools typically do not allow such easy switching between representations. For example, in Rational Rose, you configure the tool to treat certain objects such as Strings as basic types, so that during reverse engineering/code generation, they are rendered as attributes (i.e., member fields), rather than associations.
Note also that unlike UML tools you may have used, Class Designer does not support bi-directional associations. If you want a reciprocal link between the StockDeal class and the Customer class, you'll have to add a new association explicitly.
The purpose of adding that association was to record against a Customer the lastDeal that he or she placed, and by associating with the StockDeal abstract class we can record the last deal regardless of whether it was a StockSale or a StockPurchase.
The lastDeal association is a one-to-one association — for each Customer, there is only one lastDeal (i.e., the last purchase or last sale). What if we wanted to record a history of all deals made by a customer? That would imply a list, collection, or array, or — in UML parlance — a one-to-many association.
In Class Designer, it is not possible to draw a one-to-many association at all, unfortunately. Suppose, however, you already have a one-to-many relationship represented in Visual Basic code, like this:
Dim dealHistory(12) As StockDeal
Public Property dealHistory() As StockDeal
Or in Visual C# code that takes advantage of the Generics feature, like this:
private List<StockDeal> deals;
Including such a member in the Customer class would enable that class to hold a list of previous deals performed by that customer, but how will that relationship be rendered by Class Designer?
Class Designer will render an association line between the Customer and StockDeal classes in the fashion of the lastDeal association shown in Figure 5-4, except that this time the association will be named dealHistory and will have a double arrow at the "many" end of the association line, as you can see in Figure 5-5.
Whereas with one-to-one associations you have two options for visualization, Show as Property and Show as Association, for one-to-many associations you have a third option: Show as Collection Association. That's how we caused the double arrowhead to appear in Figure 5-5; and in the case of our Visual Basic examples, that double arrowhead is the only notational difference you'll see between the "Show as Association" and the "Show as Collection Association" display modes. In the case of the C# generics example that we gave, however, the effect will be as follows:
Show as Collection Association will render an association between the Customer and StockDeal classes pretty much as shown in Figure 5-5, with a double arrow at the "many" end of the association.
Show as Association will render an association line between the Customer class and a new Generic Interface type labeled IList<T> — with a single arrow because the association is to a single "list" of stock deals even though it was to "many" of the stock deals themselves.
You can find out more about how Visual Studio 2005 determines one-to-many associations by reading R. Ramesh's weblog entry "Collection associations in Class Designer" at http://www.blogs.msdn.com/r.ramesh/archive/2005/02/01/364862.aspx.
The StockDeal, StockPurchase, and StockSale classes represent the entity classes of our problem domain. These may well be refinements of entities identified during analysis (perhaps modeled in Visio), and they may well be candidates for persisting away in a database.
Those classes will not be at all useful unless we have a set of methods — an API — to manipulate them. You can define a very simple API in the form of a StockBroker class with two methods that have the following signatures:
Public Function buyStock(ByVal stockSymbol As String, ByVal numberOfShares As Integer) As StockPurchase Public Function sellStock(ByVal stockSymbol As String, ByVal numberOfShares As String) As StockSale
You can add a StockBroker class to a class diagram and use the Class Details window to define those methods, as shown in Figure 5-6. Click the <add parameter> indicators to fill out the method signatures, and notice that the return types of these methods are the StockPurchase and StockSale domain classes that were defined previously.
If you think back to Chapter 2, you'll recognize these method signatures as almost identical to those provided by the StockBroker DealingService Web service, apart from those structured return types that we had not yet conceived of when devising the Web services originally because we hadn't used Class Designer.
For a pure Web services implementation, the StockBroker API library class just defined is not strictly necessary because you could modify the return types of the Web services to match, and then insert into the Web service methods whatever implementation code would go into the methods defined above. However, we like the idea of a separate API class that encapsulates the underlying implementations and which may be fronted by the Web services or any other faade, such as a .NET Remoting object, or even invoked directly in cases where no remote invocations are required.
Remember that you can choose to see method names, names and types, or full method signatures on Class Diagrams by selecting Class Diagram Change Members Format.
The bulk of our discussion so far has been centered on class types, which is hardly surprising as this is Class Designer. However, this designer provides the capability to represent language types other than classes, and in that respect it might be thought of more properly as a Type Designer.
Figure 5-7 gives examples of the other types that may be represented on a class diagram: Structure (or Struct), Enum, Delegate, and Module.
The following sections describe each of those additional types in turn.
Chances are good that when designing a StockQuoteService in real life, you would want that service to return not only the latest stock price, but also the following:
The StockSymbol to which the quote relates, which would have been passed as input to the service
The QuoteAge, so that we have an indication of how current — or not — that quote is
An indication of the Market (LSE, NYSE, etc.) from which the quote was obtained, as a single company may trade shares in more than one market
You could therefore create a StockQuote structure to be returned as a value type from the getQuote Web service defined in Chapter 2.
Structures may contain many of the same members as classes, as you can see in Figure 5-7. There is a QuoteAge public property, actually derived from a QuoteDateTime private field. There are public fields indicating the StockSymbol and StockPrice. Finally, there is Market field, marked as friend because we want to account for the market within our implementation but not make this information generally available.
Earlier in this chapter we introduced the idea of a StockDeal class to record each deal that has been enacted for a customer. A deal will pass through a number of stages: placed (the customer has placed the deal instruction and could still cancel it), pending (the deal has not yet been done but is in a queue and cannot be canceled), and dealt (the customer has now bought or sold the shares).
Crucially, these status changes occur in a set order, so they could be coded numerically as 1 (placed), 2 (pending), and 3 (dealt). It's far better to use an enum that allows meaningful names as well as an implicit ordering. This is the DealStatus enum in the figure, the underlying code for which is as follows:
Public Enum DealStatus placed = 1 pending = 2 dealt = 3 End Enum
In a full commercial implementation of a stockbroking system, you might expect one or more classes to be notified, in callback fashion, when a deal has changed status from placed to pending or pending to dealt. This functionality could be provided through a delegate such as the DealNotificationDelegate shown in Figure 5-7.
A delegate is essentially a method signature that must be implemented by any classes that wish to receive the notification. In that respect, you might expect a delegate to be visualized as a member with parameters, but in fact it is visualized as a type having members that correspond with the delegate parameters.
For completeness, Figure 5-7 includes a module type that is only applicable to Visual Basic. This DealingUtilities module has a corresponding source file named DealingUtilities.vb.
An interface may be represented as a lollipop shape emanating from a class, or as a type shape in its own right. Figure 5-8 shows both representations: a lollipop emanating from the Control class annotated with the names of all the interfaces implemented by that class; and three of those interfaces — IDisposable, IComponent, and IDropTarget — rendered separately as shapes with member lists.
You can toggle to your preferred representation as follows:
To view an interface in full as a separate shape, right-click the interface name on the lollipop (not the lollipop itself) and choose Show Interface.
To remove an interface from your diagram simply right-click the shape and choose Remove from Diagram. This does not delete the interface code itself or remove the interface name from the lollipop.
UML tools will typically show an inheritance relationship between a class and an interface that it implements, if the two are on the same class diagram. With this Class Designer, the only indication that an interface is implemented by a particular class is its presence on the lollipop.
Showing an interface separately is beneficial in terms of visualizing the interface members, and to see relationships between interfaces themselves; such as IComponent and IDisposable in the figure. The downside is that it takes up more space on the diagrams and is unnecessary if all you really want to see is the list of interfaces supported by a class.
You can save more space in diagrams by collapsing the interface lollipop completely. Just right-click the lollipop itself (not one of the interface names) and choose Collapse. The result will be an unlabeled lollipop indicating that the class supports one or more interfaces, but you have to guess which ones. To see which ones, you can right-click again and choose Expand.
When you add interfaces to a diagram, along with classes that implement them, the designer is quite clever. Drawing an inheritance relationship between a class and an interface not only attaches that interface to the class, but also copies the interface members down to the class automatically.
After you have fine-tuned one or more class diagrams, you can copy them into Microsoft Office documents and print them. You can copy one, several, or all the shapes from a class diagram into other documents. Whether the copy operation duplicates the visual image of the shape or its underlying code depends on the type of document into which you paste it:
To copy a single element: Right-click the shape and choose Copy to place the shape on the Clipboard.
To copy several elements: On the diagram surface, drag the pointer to select the shapes you want to copy, right-click one of the selected shapes, and choose Copy to place all selected shapes on the Clipboard.
To copy all the elements in a class diagram: Right-click the diagram surface and choose Select All. Then click the Copy icon on the standard toolbar or select Copy from the Edit menu to place all of the diagram's shapes on the Clipboard.
Once you have copied your selection, paste it using the Paste command in the destination program.
Text-oriented programs will display the code behind the selected shape or shapes, while graphics-oriented programs will display the image of the shape or shapes that you copied.
To print a class diagram directly from Visual Studio 2005, just click Print on the File menu and the entire class diagram will print.
Another way to reproduce class diagrams in a format suitable for printing or inserting into documents is to choose the Class Diagram Export Diagram As Image menu option; or right-click an empty portion of the diagram and choose this option from the context menu. The resulting Export Diagram As Image dialog is slightly misnamed because it actually allows you to multiple-select from all of the available class diagrams in your solution. An interesting way in which this feature could be used is as follows:
Export image files to a folder.
Insert the images into your design document using Word's Insert Link to Image feature.
Export the diagrams to the same folder at regular intervals so that the images in Word are kept automatically up-to-date.
Although Class Designer has much the same look-and-feel as the Distributed System Designers (Chapters 2 through 4), the Export Diagram as Image feature is peculiar to Class Designer. It is not possible to export images from Application Designer, Logical Datacenter Designer, System Designer, or Deployment Designer in this way.