Design Patterns - Atai-Askarov/soen-343 GitHub Wiki
Command Design Pattern
- Command (Base Class / Interface)
Acts as the blueprint for all specific command actions. It defines the common structure and ensures every command has core properties (like ID, status, timestamp) and can be executed.
- Concrete Commands (e.g., CreateEventCommand, CustomCommand)
Represent specific, individual actions the system can perform (like "create an event" or "perform a custom action"). Each one contains the details and logic needed to carry out its particular task when its execute method is called.
- CommandInvoker
Manages the direct execution of commands. It can hold a queue of commands, trigger their execution (one or all), and potentially handle undoing the last executed command (if supported by the command itself). It's not typically involved in the approval process.
- CommandService
Acts as the persistence and approval layer. Its main job is to save command requests (usually to localStorage), load them, and manage their lifecycle. Admins interact with this service to approve or reject pending commands, which then triggers the actual execution of the approved command by this service. It's the gatekeeper for actions requiring oversight.
- useCommandManager (React Hook)
Provides a React-friendly interface specifically for interacting with the CommandInvoker. It helps UI components manage the state related to direct command execution (like tracking pending commands in the invoker's queue, knowing if execution is in progress, and seeing the last result), separate from the CommandService's approval workflow.
Creating more Commands
STEP 1: CREATE COMMAND
When creating more commands the first thing to do is to create a new file at src/components/Command/DeleteEventCommand.js
The command should look like this:
import { Command } from './Command';
export class CustomCommand extends Command {
constructor(param1, param2) {
super(
'CustomCommand', // Command type identifier - MUST BE UNIQUE
`This describes this custom command using "${param1}" and ${param2}` // Human-readable description
);
// Store all parameters needed for execution
this.param1 = param1;
this.param2 = param2;
}
// This method will be called when the command is executed
async execute() {
try {
// Implement the actual operation here
// For example, making an API call:
const response = await fetch('http://localhost:5000/api/endpoint', {
method: 'POST', // or GET, PUT, DELETE as appropriate
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
param1: this.param1,
param2: this.param2
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Operation failed');
}
// Return result data if needed
return await response.json();
} catch (error) {
console.error('Command execution error:', error);
throw error; // Re-throw to be handled by the command service
}
}
// Add any additional methods needed for your command
getDetails() {
return {
...super.getDetails(),
param1: this.param1,
param2: this.param2
// Add any other properties you want to expose
};
}
}
STEP 2: UPDATE COMMANDSERVICE
You need to add your command to CommandService so other components can use it
async approveCommand(id) {
// ....
}
// ....
try {
// Recreate the proper command object based on the stored data
let executableCommand;
switch (command.type) {
case 'CreateEvent':
const { CreateEventCommand } = await import('../components/Command/CreateEventCommand');
executableCommand = new CreateEventCommand(command.eventData);
break;
case 'CustomCommand': // 👈 ADD YOUR NEW COMMAND CASE HERE
const { CustomCommand } = await import('../components/Command/CustomCommand');
executableCommand = new CustomCommand(command.param1, command.param2);
break;
// Add other command types here...
default:
throw new Error(`Unknown command type: ${command.type}`);
}
// Rest of the method remains the same...
} catch (error) {
// Error handling...
}
}
STEP 3: CREATE COMPONENT THAT TRIGGERS THE COMMAND
This is where your component would normally perform the command; however, since we want only admins to approve it, we need to use the command pattern
const CustomButton = ( {param1, param2}) =>{
handleSubmit = async() =>{
try{
//...
const command = new CustomCommand(param1, param2); //Create it
commandService.addCommand(command); //Add it to the service
// Show success message
setMessage({
type: 'success',
text: 'Your request has been submitted for approval.'
});
} catch(error) {
//.. Error handling
}
}