Design of the Photo Editor Application
We have analyzed the requirements and have defined what the system is supposed to do. The next step is to decide how the system will be implementedin other words, to design the system. |
A standard way to document software architectures is to use the Unified Modeling Language (UML). We mainly use UML class diagrams to define the structure of the photo editor software system. The design shows classes, attributes, operations, and the relationships among them. If you have a working knowledge of UML, you may want to skip the following brief introduction to UML class diagrams and go immediately to the provided class diagram of the photo editor in section 5.4.2.
If you're not familiar with UML, we offer a very short introduction to the class diagram in UML. This introduction is not intended as a tutorial on UML class modeling but rather aims to build a common vocabulary to help you understand the design of the system throughout the book. Therefore, the goal of this section is to give you just enough details to understand the class diagrams developed for the application. For more detailed information on UML and class diagrams, see the references section in Chapter 4.
5.4.1 Brief Introduction to UML Class Diagrams
The basic element of a class diagram is a class, which you model using a rectangular box. This box can contain the class name by itself and can also contain the class attributes and the operations defined by the class. Figure 5.3 shows sample class definitions.
Figure 5.3. Sample Class Models
Figure 5.3A shows a class that has sample attributes and an operation definition. The sampleOperation call returns a uint value, which is shown after the operation name and separated from it by a colon. In addition, the visibility of attributes and operations is shown in the class model by means of the provided prefix. Table 5.2 lists the supported visibility prefixes and their meanings. As you can see, in SampleClass the attributes are private and protected, whereas sampleOperation is defined as public.
Prefix |
Visibility |
---|---|
- (hyphen) |
private: Can be seen only within the class that defines it |
# |
protected: Visible within the class that defines it and by subclasses of the defining class |
+ |
public: Visible to any class within the program |
The sample class shown in Figure 5.3B is an abstract class. Abstract class names are shown in italics to distinguish them in the class diagram. Note, too, that the public operation GetSize() returns a long value, which actually is the private attribute size of SampleAbstractClass. The private attribute size is initialized to 0 (zero) and of type long.
An abstract class without any implementation in UML is called an interface class. Figure 5.3C shows a sample interface class identified by the stereotype <> above the class name. Interface classes provide no implementation, and this means that no section for attributes is needed in the class model. The operations in an interface class are shown in italics because they do not provide any implementation and must be implemented by the derived classes.
UML also lets you define parameterized classes. In C++ this feature is known as a template class (C# will provide support for parameterized or generalized classes in a coming version). Parameterized classes are identified by the parameter definition in the upper-right corner of the class model. Figure 5.3D shows a sample parameterized class.
The class shown in Figure 5.3D is a simple class showing only the class name. This type of model is usually used in overview diagrams of large systems.
To model the system, we must define relationships between the classes. The next section introduces the class relationship models in UML.
Class Relationships
Figure 5.4 shows the basic dependency principles among classes.
Figure 5.4. Class Dependency
Figure 5.4A shows a dependency between Class1 and Class2. The dependency indicates that changes to Class2 could trigger changes in Class1. Figure 5.4B shows navigability in both directions. Navigability shows the directions (unidirectional or bidirectional) in which the program can traverse the class tree. In the example, Class6 "knows" about Class5, and vice versa. The navigability diagram also shows how to use notes to add information to the class diagram if necessary. At both ends of the navigation relationship, you can show any multiplicity of the relation (see Figure 5.4D) for more detail).
Figure 5.4C shows an association relationship between two class instances (or objects) in addition to a note. The class instances are identified by the prefix object and the class name, which are separated by a colon and underlined. Both ends of an association can be labeled. The label is called a role name.
Another important part of class diagrams is the ability they give you to express multiplicities, as shown in Figure 5.4D. Multiplicity is expressed by a number on each side of the dependency connection. If no number is supplied, then the default, which is 1, is assumed. The meanings of the multiplicities numbers are as follows:
1 |
Exactly one |
* |
Many (zero or more) |
0..1 |
Optional (zero or one) |
1..* |
One or more |
0..* |
Optional, one or many |
2..9 |
Example of an m..n relationship; in this case, a two to nine dependency |
Now let's look at the more advanced class dependencies, as shown in Figure 5.5.
Figure 5.5. Advanced Class Dependencies
Aggregation (shown in Figure 5.5A) is a part-of relationship. For example, a point is part of a circle. This sounds very simple, but then what is the difference between dependency and aggregation? Aggregation can be seen as a placeholder. In other words, you can use aggregation as an indication of a relationship without specifying the details of the relationship. This is possible because UML defines very little of the semantics of aggregation.
In addition to aggregation, UML offers a more defined variation called composition. Composition implies that the part-of object belongs only to one whole, and the part-of object's lifetime responsibility is with the object that contains it. Figure 5.5B shows that Class2 is part of Class1 and that Class1 is responsible for creating and deleting objects of Class2.
Another dependency defined in UML is the generalization, shown in Figure 5.5C. Generalization can be seen as an is-a relationship. For example, a diesel engine is an engine, and a gasoline engine is an engine. This holds true if "engine" is defined as the commonalities of diesel and gasoline engines. Every diesel engine and every gasoline engine can then be defined by deriving from the general engine class and adding the specifics according to the engine type.
Interfaces and their implementation can be modeled within UML in two ways, as shown in Figure 5.5D. The first technique is to define an interface class that does not contain any implementation. This class is then connected to the implementation class via the realization dependency. In the example, Class8 calls on the Interface method, which is implemented in the ImplementationClass. The second way to show an interface in a UML diagram is by using an Implementation class that shows the provided interface with the lollipop representation. In the example, Class9 calls the Interface method of AnotherImplementationClass. In the first technique, you can explicitly show the interface definition and the implementation, whereas the second technique shows only the implementation class.
The use of parameterized classes is called bound elements and is known to C++ programmers as template classes. In Figure 5.5E, BoundElement is bound by a short parameter to SampleParameterClass. Parameterized classes cannot be extended by the bound class. BoundElement can use completely specified types from the parameter class. The only thing added to the parameter class is the restricting type information.
Association classes are used to add an extra constraint to a relation of two objects. This is shown in Figure 5.5F. Only one instance of the association class can be used between any two participating objects. In the example, a Person is working for at most one Company at a time (at least in theory). An association class is used to keep information about the date range over which each employee is working for each company. Because persons sometimes switch jobs, this information should not be kept within the person's class directly but rather in an association class.
In object-oriented programming, you usually try to limit dependencies to the absolute minimum. In this way, changes made to one class do not trigger endless changes to other classes. Necessary dependencies are usually kept on the interface level.
5.4.2 Design of the Photo Editor Application
The design of the photo editor application is done in Microsoft Visio. The decision to use Visio was made because in addition to providing tools to generate UML diagrams, Visio lets you generate code from class models. Also, Visio supports extracting class models from existing code, a capability that can be very useful when you're reengineering poorly documented software. Unfortunately, Visio has no support for the creation of XML documentation from the UML models. Therefore, during document review we must trace requirement keys manually through the design process.
The class diagram shows that the main application class, called PhotoEditorForm, is derived from the Windows.Forms class. The Windows.Forms class provides Windows-related functionalities that are used by the photo editor. In addition, PhotoEditorForm creates an instance of ExceptionPublisher, which is derived from the IExceptionPublisher interface provided by the Microsoft application block. In addition, an instance of the Picture class is created. The Picture class instance holds the loaded image and provides the basic image-manipulating operations (such as rotating and flipping).
Figure 5.6 shows the class diagram for the photo editor application in this iteration.
Figure 5.6. The Photo Editor Class Diagram