Design of the GDI+ Extensions

Based on the analyzed requirements, we specify the design. The functionalities to be implemented are text and graphics primitives. It would be convenient for the application to be able to treat all graphical overlays, such as circle, line, and rectangle, generically and not have to know which object it is actually dealing with. For example, if the application would like to draw a graphical overlay object it would simply call a draw method without knowing whether the object to be drawn is a rectangle, a line, or a circle.

We can achieve this behavior if we arrange the graphical objects in a tree structure with a general graphical object as parent. So one goal of the design is to specify the design in a tree structure that allows the application to treat all the primitives and compositions of them uniformly.

6.3.1 Using Design Patterns

The foregoing problem description sounds like a problem that has been solved by other programmers many times before. Therefore, it might be worthwhile to see whether an abstract solution to the stated design problem is available. If it is, we can adapt it to fit the needs of the photo editor application.

An abstract solution to a particular software design problem is called a design pattern. It is a solution to a problem that occurs in the same form over and over again. A design pattern is formulated so that it can be applied whenever the problem occurs. Design patterns are usually described in UML, because it is a common language for software design. One of the advantages of describing a design in UML is that it is independent of the programming language or the domain.

Many programmers have used design patterns, perhaps even without knowing it. Design patterns are not new inventions but rather are collections of reusable solutions for common software design problems. For the programmer they are a reference to solutions that can be adapted to concrete problems. For inexperienced programmers, design patterns can teach them how to avoid common problems by using the solutions that are provided by experienced colleagues. The first published collection of design patterns was the book Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (for more information, see the references section). These authors are also known as GoF, which stands for Gang of Four. This book contains 23 design patterns for object-oriented software design and is the reference used in this book.

The patterns in the design patterns book are divided into three categories: creational, structural, and behavioral. Each described design pattern is structured via four essential elements:

The patterns in Design Patterns are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.

To identify the applicable design pattern (if it exists) that suits the needs of the GDI+ extension of the photo editor, we review the design problem: The image and graphical objects are to be represented by a tree structure. The PhotoEditorForm class needs to treat all objects in the tree uniformly.

With this problem statement in mind, we search the 23 published design patterns for an applicable design pattern.

Using the Composite and Iterator Design Patterns

The type of problem we have described should be located in the section of the book devoted to structural patterns. Therefore, we review only the seven published design patterns in that category for applicability. We find that the description of the Composite design pattern fits the design problem of the photo editor application very well.

In fact, the Composite pattern states exactly the problem that needs to be solved in this chapter. Therefore, we identify the Composite pattern as a possible abstract solution and analyze it in more detail. Figure 6.2 shows the static structure of the Composite design pattern.

Figure 6.2. The Composite Design Pattern

Based on the class diagram, the participants and their responsibilities are described in Table 6.2.

The client uses the interfaces defined by the component. If a leaf is called directly, the operation will be executed, whereas if a composite is called then the composite might do some preprocessing and then forward the call to the operation on the leaf nodes.

Based on the abstract solution, we adapt the solution to solve our specific design problem. Therefore, the client that is defined corresponds to the application that calls operations on either the leaf or the composite.

Table 6.2. Participants in the Composite Design Pattern

Participant

Responsibility

Client

Calls the interfaces provided by the component to manipulate leaf and composite objects.

Component

Declares the interface for the objects in the composition and implements default behavior for the common interfaces if appropriate. In addition, declares the interfaces to access and manage child components. Optionally the component can also define interfaces to access the parent of an object.

Leaf

Represents a leaf object of the composition (leaves do not have children) and implements the manipulators for the primitive objects in the composition.

Composite

Defines the behavior for components that can have children and stores the child components. In addition, it implements child-related operations in the component interface.

The composite is equivalent to the class Picture. The Picture class can contain a list of graphical components. The components themselves are leaf nodes and cannot have any children. The component class will be an abstract class that provides the common functionality. Figure 6.3 shows the class diagram for the photo editor, including the adapted Composite design pattern.

Figure 6.3. The Photo Editor Using the Composite Design Pattern

In addition, the Picture class must provide a list that stores the components created. The list must provide the functionality to add, remove, and clear the list. In C++ this is very often done by using a pattern called Iterator. The Iterator design pattern provides sequential access to elements of an aggregate object, such as a list, without exposing the underlying representation. In addition, the Iterator pattern lets us traverse the list in various ways without having to define many iterator interfaces.

To accomplish this, the Iterator pattern separates the iterator from the list (see Figure 6.4). An abstract iterator and an abstract list are defined. The abstract class defines the common interfaces for the iterator and list. The derived, concrete iterator and list then implement the functionality.

Figure 6.4. The Iterator Design Pattern

Table 6.3 shows the participants of this design pattern and their responsibilities.

Table 6.3. Participants of the Iterator Design Pattern

Participant

Responsibility

Aggregate

Provides an interface for creating an Iterator object.

ConcreteAggregate

Implements the creation of the Iterator and returns an instance of the correct ConcreteIterator.

Client

The client that uses the list and iterates through it.

Iterator

Provides the interfaces for accessing and traversing elements.

ConcreteIterator

Implements the Iterator functionality and keeps track of the current position in the traversal of the aggregate.

The shown concept of asking the aggregate object to create the corresponding Iterator is also known as the Factory method design pattern. The Factory method uses two class hierarchies: one for the aggregate and one for the Iterator. The CreateIterator method sets the connection between the two.

This example shows that design patterns are often used together with other design patterns to solve complex design problems. For the photo editor application it is not necessary, though, to go overboard with any more design patterns. The only other thing we need is a list, which enables us to retrieve, add, remove, and clear components.

Figure 6.5 shows the complete static class diagram for the implementation of this iteration. The next step is the analysis workflow.

Figure 6.5. The Added List Component

Категории