Martin Doms' Design Study
Martin Doms (Talk | contribs) (→Commands) |
Martin Doms (Talk | contribs) (→Martin Doms' Design Study) |
||
Line 47: | Line 47: | ||
Note than on the diagram I have not shown any dependencies between the model and command objects. This was done to keep the diagrams understandable. Almost every command will have some dependency or containment relationship with one or more model objects, but each command will have different dependencies because they are object-specific. The model and commands are tightly coupled necessarily. | Note than on the diagram I have not shown any dependencies between the model and command objects. This was done to keep the diagrams understandable. Almost every command will have some dependency or containment relationship with one or more model objects, but each command will have different dependencies because they are object-specific. The model and commands are tightly coupled necessarily. | ||
+ | |||
+ | === Patterns and Practices === | ||
+ | The following design patterns can be found in this design: | ||
+ | ====Composite==== | ||
+ | The model implements composite pattern at the top level of abstraction. JavaComponents may be either leaf or composite components, with composite components containing a collection of JavaComponents. This is used because of the tree-like nature of a Java program, with packages nested inside of projects, types nest within packages etc. The reason I didn’t use regular containment (ie, packages containing a Set of types, etc.) is because of the complex nature of this tree – the rules around which items can be nested in which are complicated. For example, classes can be nested within classes, packages within packages and classes within classes, but not packages within classes. | ||
+ | |||
+ | We can control which components are allowed to be nested in which with business rules in the addChild method. | ||
+ | |||
+ | I have also used a modified version of Composite pattern in the command system. The CompoundCommand class can be thought of as a composite of commands and can even be composed of other CompositeCommands. In this case we have not used a particular leaf class, as all non-compound commands are assumed to be leafs. | ||
+ | |||
+ | ====Decorator==== | ||
+ | The decorator pattern is used to add behaviour and data to generic and array types in the model. For a complete description see the Model section above. This implementation of the decorator pattern is a classical one with nothing fancy going on. | ||
+ | |||
+ | ====Command==== | ||
+ | Quite obviously, the Command system implements the Command pattern .The advantages of this were described above, in the Command section. In this implementation the following items correspond to these Gang of Four labels | ||
+ | * Command: Command. | ||
+ | * ConcreteCommand: Any of the concrete subclasses of command. | ||
+ | * Client: The calling application. | ||
+ | * Invoker: Probably supplied by the client application. This could be either the same module as Client, or more often, a command stack of some kind to allow for undo and redo operations. | ||
+ | * Receiver: Model objects. |
Revision as of 09:35, 25 September 2010
Contents |
Martin Doms' Design Study
The problem
This design study is based on a project that my team in the COSC325 Group Project full-year class built. The full system is a round-trip engineering tool that allows users to generate and modify UML diagrams from existing code, and push modifications through to the code. The program exists as a plugin for Eclipse written in Java.
The aspect of the project I am working on redesigning is the model and command infrastructure. The existing system is a transactional system, which means that the user makes arbitrary modifications to the UML representation and at any time can commit those modifications to the code as a single unit of work. Transactions are summarized for the user before committing.
There are two main problems with the existing system that I wish to solve. The first is in the model. The model is essentially a simplified representation of the code which is derived from a much more complex abstract syntax tree, provided by the Eclipse platform. The main problem with the current model is that due to poor design decisions early in the project (to be discussed later), modifications and extensions to the model are difficult. Many changes to the model result in bugs that would not exist in a more robust system.
The second issue is the command system. In the current system, UI actions result in the generation of Command objects which act on the model and the underlying code. For example, if the user renames a class on the UML diagram, the rename action afforded by the UI generates a rename Command object which is executed. There are no dependencies between the UI actions and the model, as the command infrastructure acts as an insulating layer between them. Commands can also be generated by events that occur on the file system, such as a file system listener creating a remove type Command object when the user deletes a source file.
Because of the transactional nature of the program, actions that occur on the UI currently generate two commands: a command that changes the UI (called a CommittableCommand) and the command to push that change into the code (called a CommitCommand). The CommittableCommand object is in charge of creating its own CommitCommand. This system worked well early in the project but as the program grew it has resulting in an explosion of CommittableCommand and CommitCommand subtypes which have become difficult to manage and often duplicate code.
The Redesign
The following are two UML diagrams showing the redesign of the system, followed by an explanation of them.
Model
The model UML diagram is necessarily complex, but is not difficult to understand. JavaComponent is the base class for all elements that are represented in a Java program or source file. JavaComponents can be composed of other JavaComponents (for example, Classes are composed of methods, fields and inner classes). Other elements are no composed of JavaComponents, such as methods, parameters and fields.
It could make semantic sense to say that JavaMethods are composed of JavaParameters, but I elected to keep methods as leaf nodes and contain parameters as lists because the order of a parameter is important in a method declaration and at the abstract level I defined child nodes in the composite as Sets, not lists.
The decorator pattern was used under JavaType to model arrays and generic types. The main reason for this is that for the purposes of this model we often want to treat certain types as identical to their array or generic versions, while still being able to leverage the additional behaviour and data of the decorated versions. For example, if we are modelling a class diagram in which ClassA contains a field ClassB[10], then we still want to maintain an association between ClassA and ClassB on the diagram, and it would make no sense to have a class ClassB[10] on the class diagram. The decorator pattern allows this to be done fairly easily.
JavaRelationships are simple types that relate two JavaComponents. Each relationship has a source and target JavaComponent. In a UML class diagram these would be used for things like inheritance and dependency relationships, as shown in my UML diagram. They could also be used to model relationships between packages, messaging relationships and any kind of relationship where two Java elements are involved. Relationships in this manner are implicitly directed (ie, they have a source and a target) but clients are free to ignore this directionality.
In this model relationships are generated and instantiated lazily as needed. Client code would be advised not to store collections of relationships, but allow JavaComponents to supply the relationships on demand. For example, asking a JavaClass for all of its source relationships would result in a collection containing a relationships representing its superclass connection, implementation connections and association connections sources from itself.
Because of this behaviour, clients can easily extend the system to allow for additional relationship types. For example if the client was modelling a UML Sequence Diagram, they would be free to extend JavaRelationship with a MessageRelationship subclass and extend the behaviour of JavaType to allow for this.
Commands
In the wider context of the project I am working on, interactions with the Model occur through commands. The benefits of this are
- Full separation of the UI from the model. Actions on the UI result in instantiation and execution of Command objects. Changes in the model can update the UI via the Observer pattern. This allows clients to choose from a number of different presentation patterns, including Presentation Model or MVC.
- Clients can choose to implement a command stack or use an existing command stack in another framework, allowing for simple implementation of undo and redo operations.
- Commands can behave in a transactional manner, and commands can be composed into larger composite commands allowing for complex transactions composed from simple and easy to write commands.
In the Trip project for which this system is designed, some commands result in immediate changes in the model, while others require commands to be queued for later committing. An example of this is a Rename Class command. The class on the diagram that the user updates must be updated in the model immediately to reflect the change to the user, but the change is not pushed to code until the user commits this change.
To achieve this, we generate three commands in the action. The RenameTypeCommand renames the type in the model. A RenameTypeInCodeCommand renames the type in the code through a data layer (IModelProvider, not shown, as it is out of the scope of this design project). These two commands are added to a TransactionalCommand, which is a type of command which has an immediate and a queued component. When the TransactionalCommand is executed, it executes the immediate command immediately, and queues the queued command in the transaction with which it is associated. Either of these commands could be a compound command, so a single transactional command could have multiple effects.
One of the biggest advantages to using the Command pattern is the ability to define actions on the model in a behaviour-centric (as opposed to data-centric) way. As an example, if the user issues an action to rename a class, the user interface programmer defines this behaviour as a RenameClassCommand, rather than calling a [modelObject].setName() method. This declarative style of programming can lead to fewer errors and abstracts any difficult behaviour involved with model actions into small, well-defined classes.
Note than on the diagram I have not shown any dependencies between the model and command objects. This was done to keep the diagrams understandable. Almost every command will have some dependency or containment relationship with one or more model objects, but each command will have different dependencies because they are object-specific. The model and commands are tightly coupled necessarily.
Patterns and Practices
The following design patterns can be found in this design:
Composite
The model implements composite pattern at the top level of abstraction. JavaComponents may be either leaf or composite components, with composite components containing a collection of JavaComponents. This is used because of the tree-like nature of a Java program, with packages nested inside of projects, types nest within packages etc. The reason I didn’t use regular containment (ie, packages containing a Set of types, etc.) is because of the complex nature of this tree – the rules around which items can be nested in which are complicated. For example, classes can be nested within classes, packages within packages and classes within classes, but not packages within classes.
We can control which components are allowed to be nested in which with business rules in the addChild method.
I have also used a modified version of Composite pattern in the command system. The CompoundCommand class can be thought of as a composite of commands and can even be composed of other CompositeCommands. In this case we have not used a particular leaf class, as all non-compound commands are assumed to be leafs.
Decorator
The decorator pattern is used to add behaviour and data to generic and array types in the model. For a complete description see the Model section above. This implementation of the decorator pattern is a classical one with nothing fancy going on.
Command
Quite obviously, the Command system implements the Command pattern .The advantages of this were described above, in the Command section. In this implementation the following items correspond to these Gang of Four labels
- Command: Command.
- ConcreteCommand: Any of the concrete subclasses of command.
- Client: The calling application.
- Invoker: Probably supplied by the client application. This could be either the same module as Client, or more often, a command stack of some kind to allow for undo and redo operations.
- Receiver: Model objects.