We conclude our miniseries on developing a real-world, professional drawing editor with Java by examining its control classes, often a neglected aspect in designs.
Welcome to the last part of our series. In the first part, we presented the symbol hierarchy. In the second part, the remaining classes were discussed, namely the canvas and the actions mechanism. Now, we are left with the control classes, often a neglected aspect in designs. Finally, we will look at the overall class diagram.
The source code and the executable code are available for download.
The Mediator Pattern
In any nontrivial user interface, a coordination mechanism for the various widgets is essential. In our simple drawing editor, we need to coordinate the various commands with the user's input and the current state of the user interface. A classic design pattern comes to the rescue here, the Mediator pattern. We will see an interesting implementation of it, in our Director class. This is the scenario: The user interacts with the GUI, the objects change their state, and widgets vary states accordingly. For example, whenever we select a symbol by clicking on it, it becomes highlighted its internal state has changed, so its appearance does, too and the cut, copy, delete, and properties buttons get enabled. When we deselect the symbol, the state of the buttons changes again. Our case is a simple one and we could use simple, non-centralized solutions, but when the logic is a bit more complex, coding and maintainability could be hell.
But before we start discussing the chosen design, let's recap the Mediator pattern. In the design patterns bible (Design Patterns: Elements of Reusable Object-Oriented Software, Gamma et al., Addison Wesley Ed., 1994) the Mediator pattern is defined as follows: [The Mediator pattern] defines an object that encapsulates how a set of objects interacts. It promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. Its structure is made up of a interface, to which refer. The class is the common superclass to all the classes to be directed by the Mediator. If a common superclass cannot be found, we can use an ad hoc interface that all Colleagues must implement. is a placeholder for the concrete subclasses of . Often, are subclasses of or some of its subclasses. With an event-driven messaging system, can notify the Mediator that their state is changed, so that the Mediator can impose the correct state on the other .
Figure 1. The generic structure of the Mediator pattern.
|Figure 1. The generic structure of the Mediator pattern.|
There is a vast amount of literature on this pattern, and many variations have been proposed. The main advantages to our choice are simplicity and performance, but it has its own drawbacks. First of all, we only have to coordinate , so we can safely simplify the generic structure using them as . Then, in order to adapt the pattern to our needs, we need to provide each subclass with a reference to the , so that it can coordinate all the actions. In our case, this extra reference is not needed. As we discussed in the second part of this series, all commands are centralized in the class. This turns out to be useful now, because we can let the class invoke the on s' behalf. Of course, not all the commands pass through the Draw class, and it would be too limiting a design, anyway. We can expose the from the Draw with a method. The process is shown in Figure 2.
Figure 2: The Mediator implementation in our case.
|Figure 2: The Mediator implementation in our case.|
Another difference with the standard case is the use of several specialized methods, to reduce computation time for the most common processes. The Director's methods are called quite often during user interaction. To understand how the design has been organized, let's start from the attributes used in our class:
The actions that are affected by the state of the clipboard. This group includes commands like and These actions must be disabled when the clipboard is empty, and enabled when the clipboard contains items.
The actions that need a selected symbol in order to be executed: , etc.
The commands that are affected by the state of the instance. The action is only enabled when the action stack is not empty.
All these attributes are implemented with collections and kept private in the class. In this way, if the user selects a symbol, we only have to take care of the
commands, and without preliminary if-then checks, because when we call the method we are saying to the Director that (only) the selected symbol has changed. This technique consisting of selective methods of invocation on the Director has its own drawbacks. It is much less flexible, and breaks one of the fundamental principles of OO programming: encapsulation. Let's suppose we want to take care of other aspects, say enabling and disabling the Save button, to check if a drawing needs to be saved when the user tries to exit from the program. We need a new attribute in our Director and its related methods. This could be tricky, especially when dealing with complex cases, where the number and the nature of the objects to coordinate may vary a lot. One good solution lies between the two approaches. Use specialized methods only for the most common components coordination, and for the remaining ones a general method will keep the class simple and manageable. Unfortunately, we don't have the space here to discuss the various aspects of the design in detail.
Putting It All Together
Now we are ready for the complete picture. After having looked at all the pieces separately, from the bottom up, we can understand the whole structure, as shown in Figure 3.
Figure 3. The whole picture (static class diagram).
|Figure 3. The whole picture (static class diagram).|
In Figure 3, for clarity, we omitted the interface implementation, the subclasses of (all contained in the ), and the two repository classes. The Cloneable interface by the class is depicted in the diagram. This is accessed through the class, underlining its explicit relationship expresses the fact that we use the cloning facility in . Last, a little note on the order in collections specification. It's always a good idea to express it when meaningful. So, a drawing contains an ordered list of symbols, while there is no particular meaning in the order of the symbols in the palette.
Finally, the source code
I prefer to work on reusable design issues rather than mere implementation tricks. Many things are implemented naively, and a lot more are not implemented at all. Some worth mentioning are:
- Put in front/push back symbols.
- Mouse drag is too coarse; it needs to be refined, especially for non-bitmap symbols.
- Currently, selection of only one item at a time is implemented.
- The implementation of circle and curve symbols is just sketched.
- Only one drawing can be created at a time.
Well, the series is over. I hope you enjoyed reading it as much I did writing it. A special thanks to everybody at EarthWeb who helped this happen, with their careful (and often invisible) work. I hope my work, together with theirs, is going to be useful to you.
There are many interesting aspects to designing a drawing editor that we haven't covered in this series because of space considerations; more popular topics were chosen over less useful or rarely used techniques. You can find out more at the home page for this project. Of course, any questions or suggestions are warmly welcomed.
About the Author
Mauro Marinilli has been a Java developer since the language's early days. He's also active in academic research, mainly in information filtering, case-based reasoning, and adaptive hypermedia. He is currently working as chief engineer for the GUI team developing the new Italian Air Force Meteorological and Forecasting System.