Project: AddressBook - Level 4
AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 6 kLoC.
Code contributed: [Functional code] [Test code] {give links to collated code files}
Enhancement Added: Undo/Redo
Undoing previous command : undo
Restores the address book and task book to the state before the previous undoable command was executed.
Format: undo
/u
Undoable commands: those commands that modify the address book’s content ( |
Examples:
-
delete 1
list
undo
(reverses thedelete 1
command) -
select 1
list
undo
Theundo
command fails as there are no undoable commands executed previously. -
delete 1
clear
undo
(reverses theclear
command)
undo
(reverses thedelete 1
command)
Redoing the previously undone command : redo
Reverses the most recent undo
command.
Format: redo
/r
Examples:
-
delete 1
undo
(reverses thedelete 1
command)
redo
(reapplies thedelete 1
command) -
delete 1
redo
Theredo
command fails as there are noundo
commands executed previously. -
delete 1
clear
undo
(reverses theclear
command)
undo
(reverses thedelete 1
command)
redo
(reapplies thedelete 1
command)
redo
(reapplies theclear
command)
End of Extract
Justification
{Justify the need for, and the current design (i.e. external behavior) of, the feature}
Undo/Redo mechanism
The undo/redo mechanism is facilitated by an UndoRedoStack
, which resides inside LogicManager
. It supports undoing and redoing of commands that modifies the state of the address book (e.g. add
, edit
). Such commands will inherit from UndoableCommand
.
UndoRedoStack
only deals with UndoableCommands
. Commands that cannot be undone will inherit from Command
instead. The following diagram shows the inheritance diagram for commands:
UndoableCommand
adds an extra layer between the abstract Command
class and concrete commands that can be undone, such as the DeleteCommand
. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of the address book before execution. UndoableCommand
contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.
The details are given in this diagram.
Commands that are not undoable are implemented this way:
public class ListCommand extends Command {
@Override
public CommandResult execute() {
// ... list logic ...
}
}
With the extra layer, the commands that are undoable are implemented this way:
public abstract class UndoableCommand extends Command {
@Override
public CommandResult execute() {
// ... undo logic ...
executeUndoableCommand();
}
}
public class DeleteCommand extends UndoableCommand {
@Override
public CommandResult executeUndoableCommand() {
// ... delete logic ...
}
}
-
Suppose that the user has just launched the application. The
UndoRedoStack
will be empty at the beginning. -
The user executes a new
UndoableCommand
,delete 5
, to delete the 5th person in the address book.-
The current state of the address book is saved before the
delete 5
command executes.
-
-
The
delete 5
command will then be pushed onto theundoStack
(the current state is saved together with the command).
As the user continues to use the program, more commands are added into the undoStack . For example, the user may execute add n/David … to add a new person.
|
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.
|
-
The user now decides that adding the person was a mistake, and decides to undo that action using
undo
. -
We will pop the most recent command out of the
undoStack
and push it back to theredoStack
. -
We will restore the address book to the state before the
add
command executed.
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack .
|
The following sequence diagram shows how the undo operation works:
The redo does the exact opposite (pops from redoStack
, push to undoStack
, and restores the address book to the state after the command is executed).
If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack .
|
The user now decides to execute a new command, clear
.
-
As before,
clear
will be pushed into theundoStack
. -
This time the
redoStack
is no longer empty. It will be purged as it no longer make sense to redo theadd n/David
command.
Commands that are not undoable are not added into the undoStack . For example, list , which inherits from Command rather than UndoableCommand , will not be added after execution:
|
The following activity diagram summarize what happens inside the UndoRedoStack
when a user executes a new command:
Design Considerations
Aspect: Implementation of UndoableCommand
Alternative 1 (current choice): Add a new abstract method executeUndoableCommand()
Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with Command
do not have to know that executeUndoableCommand()
exist.
Cons: Hard for new developers to understand the template pattern.
Alternative 2: Just override execute()
Pros: Does not involve the template pattern, easier for new developers to understand.
Cons: Classes that inherit from UndoableCommand
must remember to call super.execute()
, or lose the ability to undo/redo.
Aspect: How undo & redo executes
Alternative 1 (current choice): Saves the entire address book.
Pros: Easy to implement.
Cons: May have performance issues in terms of memory usage.
Alternative 2: Individual command knows how to undo/redo by itself.
Pros: Will use less memory (e.g. for delete
, just save the person being deleted).
Cons: We must ensure that the implementation of each individual command are correct.
Aspect: Type of commands that can be undone/redone
Alternative 1 (current choice): Only include commands that modifies the address book (add
, clear
, edit
).
Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are lost).
Cons: User might think that undo also applies when the list is modified (undoing filtering for example), only to realize that it does not do that, after executing undo
.
Alternative 2: Include all commands.
Pros: Might be more intuitive for the user.
Cons: User have no way of skipping such commands if he or she just want to reset the state of the address book and not the view.
Additional Info: See our discussion here.
Aspect: Data structure to support the undo/redo commands
Alternative 1 (current choice): Use separate stack for undo and redo
Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both HistoryManager
and UndoRedoStack
.
Alternative 2: Use HistoryManager
for undo/redo
Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase.
Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager
now needs to do two different things.
End of Extract
Enhancement Proposed: Add command remark
{Explain similar to the Undo/Redo feature above.}
Project: PowerPointLabs
{Optionally (not graded), you may include other projects in your portfolio.}