UML for the Software Developer, Part 2: Mastering Associations : Page 2
Associations are a key part of the UML lexicon. Use them to define the ways in which your application's classes communicate. In this article you'll see that adding a database introduces design patterns for the first time using the façade pattern.
by Mark Goetsch
Jan 4, 2005
Page 2 of 3
Iteration 1: Adding a Database
If the system was only using the database to store and retrieve information I could directly access the database from each of the classes.
Figure 1. Adding a Database to the Example: A <<database>> stereotype is the way that the database is identified.
In the last article Broker, Client, and Security inherited the addMember, deleteMember, and listMember methods from the BrokerageCollection interface (see Figure 1). Notice in Figure 1 the addition of a line attaching BrokerageDatabase to BrokerageCollection. This is called an association.
When two classes do not inherit from one another but are related to each other it is called association. In the weakest sense association is one class referencing another class. Often this is one class using the methods of the other class.
As developers, you and I know that in the example of the BrokerageCollection using the BrokerageDatabase (see Figure 2), we can use ADO and also that the database uses SQL. If you had previous knowledge of the environment, you might know whether the database is SQL Server or Oracle. This information could be added as an additional note on the diagram. You can infer from <<uses>> that BrokerageCollection references BrokerageDatabase. How do you know it is not the other way around? The answer lies in how style rules are used in UML.
Just as in programming, there are style rules in UML. One of these rules governs which way to read the diagram. I use the left-to-right approach for class diagrams, which have the boundary classes to the left (a boundary class is one that communicates with a device, actor, or role) and the classes that represent the back-end parts of the system such as the database, to the right. This is not universal. In component diagrams with layers in them the left-to-right is replaced by top-to-bottomsometimes called a "stack." (I will cover component diagrams in another article. For now, think of a component as container for related classes.)
Figure 2. Association Between Classes: The ends of an association are roles. In this example a description represents the roles. This is a good practice when there isn't a descriptive role name.
The importance of style is to communicate these rules, which are sometimes idiosyncratic to others that need to use them. In project-level work this is either captured as an addendum or as a legend on the diagram.
Implementing the Association
In the current example the auto-generated code fails. The BrokerageDatabase does not translate into a C# class but represents the SQL Server database. Communication with a database requires a connection string instead of the reference from a C# class, which results from auto-generating the class.
BrokerCollection is also an interface. By associating BrokerCollection with BrokerDatabase, every class that inherits from BrokerCollection will also inherit the association. These issues force me to hand-code most, if not all, of the classes in the system (see Listing 1).
Because of the inheritance, the code for Broker, Client, and Security in Listing 1 are going to be the same. The extra coding required is tedious and error prone. It is also difficult to maintain the application when coded this way. If the structure of the database were to be changed, then Broker, Client, and Security would all have to change.
Iteration 2: From an Interface to a Façade
Interfaces come in two forms. The first type is the interface as a method template. Each class that inherits from the interface is required to implement all the interface's methods. In the example, each class that was implemented had to implement a version of addBrokers, deleteBrokers, and listBrokers with the same method signatures, guaranteeing that every access to the database would have this functionality. (For you COM developers, implementing a COM interface was critical. By inheriting from the IUnknown interface and implementing QueryInterface, AddRef, and Release methods, a component could interact with another component by only knowing the other components' IDs (GUID) in the operating system's registry.) When we look at a class that inherits from an interface we have a common set of expectations that the classes will support the same functionality.
In the example, each class inherited from BrokerageCollection. The problem is that even though the functionality was guaranteed we had to implement that functionality for each class. Not only that but if the database location were to change, the structure of the database changes or the database would be split between two instances of a database (partitioning the database). Each class using this interface would have to rewrite these methods. What a mess!
Author's Note: I once worked with a company that, after years of development, had not inherited from a common interface and had no common access to the databases. More than 100 databases were hard-coded among a few hundred applications. Changes to any of the databases would cause all types of unexpected problems. They finally added objects that encapsulated the database layer similar to what I've described in this article. The cost was 6 to 9 months of time for 10 developers and over $500,000 dollars. And it was still worth it.
To fix this problem I'll use another type of interface called a façade. A façade is one of the main design patterns, documented by the Gang of Four in their seminal book "Design Patterns." A façade acts as an interface between systems. I can add the façade to my current classes by adding a new class called, not unexpectedly, dbFacade.
Figure 3. The BrokerageDatabase Becomes dbFacade: The BrokerageDatabase required knowledge of how to implement it. dbFacade hides the implementation.
I've added dbFacade to my UML model (see Figure 3). Notice that the stereotype is <<facadePattern>>. UML 2.0 has a specific way of defining patterns but it would have been beyond the scope to cover that here. For now the stereotype does the job. I've kept the interface class but dbFacade does not use it because each method has an extra parameter for the type. Remember that an interface's method signatures have to match the inherited functionality and dbFacade's doesn't.
I can use my left-to-right style rule to eliminate the <<Uses>> stereotype or I can use a navigation arrow on the association, as I did in Figure 3. I prefer using the left-to-right style rule because it forces the UML modeler to order its classes, but either way will accomplish the same thing. Code generators, however, use the navigation arrow, and if the arrows are not used they generate C# classes that reference each other.
The multiplicity isn't required here either. If there are multiple instances of Broker, Client, or Security it isn't evident from the design so far. It can be surmised that dbFacade will have a multiplicity of one. Most code generators will assume a multiplicity of one unless specified otherwise.
I removed the database class BrokerageDatabase. The code that will communicate with the database is all encapsulated in the dbFacade class leaving the connection to BrokerageDatabase purely an implementation detail.