Implementing the Command Pattern in TypeScript
The Command Pattern is a behavioral design pattern that decouples an object (the invoker) from the object that knows how to perform a specific action (the receiver). This allows for queuing, logging, undoing, and replaying commands, making it a powerful tool for managing complex operations. This challenge asks you to implement the core types of the Command Pattern in TypeScript, focusing on type safety and flexibility.
Problem Description
You are tasked with creating a generic Command Pattern implementation in TypeScript. This implementation should include the following:
CommandInterface: Defines a single method,execute(), which encapsulates the action to be performed.InvokerClass: Holds a list of commands and executes them. It should be able to add, remove, and execute commands.ReceiverClass (Abstract): Represents the object that actually performs the action. This class should be abstract to allow for different receivers to be used with the same invoker. It should have a methoddoSomething()which is meant to be overridden by concrete receiver classes.ConcreteCommandClasses: Implement theCommandinterface and delegate the execution to a specificReceiver. You should create at least two concrete command classes:ConcreteCommandAandConcreteCommandB.
Key Requirements:
- Type Safety: Leverage TypeScript's type system to ensure that commands are executed correctly and that the invoker can handle different types of commands.
- Flexibility: The design should be flexible enough to accommodate different receivers and commands without modifying the core
CommandandInvokerclasses. - Clear Separation of Concerns: The invoker should not know the details of how a command is executed. It should only know that it needs to execute a command.
Expected Behavior:
- The
Invokershould be able to store and execute multiple commands. - Executing a command should trigger the corresponding action in the receiver.
- Adding and removing commands from the invoker should work as expected.
Edge Cases to Consider:
- What happens if you try to execute a command that hasn't been added to the invoker? (Consider returning a boolean indicating success/failure).
- How should the pattern handle errors that occur during command execution? (Consider throwing an error or returning an error code).
Examples
Example 1:
Input:
Invoker: invoker.addCommand(new ConcreteCommandA(receiverA));
invoker.addCommand(new ConcreteCommandB(receiverB));
invoker.executeCommand(0); // Execute the first command
Output:
receiverA.doSomething() is called.
Explanation:
The invoker executes the command at index 0, which is ConcreteCommandA. ConcreteCommandA then calls receiverA.doSomething().
Example 2:
Input:
Invoker: invoker.removeCommand(1);
invoker.executeCommand(1); // Attempt to execute the command at index 1
Output:
Returns false (command not found)
Explanation:
The command at index 1 was removed, so attempting to execute it results in a failure.
Example 3: (Edge Case)
Input:
Invoker: invoker.executeCommand(5); // Attempt to execute a command out of bounds
Output:
Returns false (index out of bounds)
Explanation:
The index is out of bounds, so the execution fails.
Constraints
- All classes and interfaces must be defined using TypeScript.
- The
Invokershould store commands in an array. - The
executeCommandmethod in theInvokershould accept an index as input. - The
executeCommandmethod should return a boolean indicating whether the command was successfully executed. - The
Receiver'sdoSomething()method should be abstract.
Notes
- Consider using generics to make the
Commandinterface more flexible. - Think about how to handle errors that might occur during command execution.
- Focus on creating a clean and well-documented implementation.
- The primary goal is to demonstrate understanding of the Command Pattern's core concepts and how to implement them effectively in TypeScript. Don't worry about complex undo/redo functionality for this challenge.