DEV Community

Olawale Afuye
Olawale Afuye

Posted on • Edited on

Command Pattern in Programming using a uber as a case study

the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters. It is also a design pattern in software development that allows us to encapsulate requests or commands as objects, allowing us to parameterize clients with different requests, queue or log requests, and support undoable operations.

To explain this pattern using an Uber app as an example, let's consider the following scenario:

Suppose you're building an Uber app, and you want to implement a feature that allows users to request a ride. When a user requests a ride, you'll need to perform several tasks, such as:

  1. Finding a nearby driver
  2. Calculating the fare
  3. Notifying the driver about the ride request
  4. Updating the user's ride history

Instead of implementing all these tasks directly in the ride request function, we can use the Command Pattern to encapsulate each task as a separate object, and then chain them together to perform the entire ride request process.

Here's how it would work:

We create a base Command interface or abstract class that defines an execute method.

We create concrete command classes that implement the Command interface or extend the abstract class. For example, we might create a FindDriverCommand, CalculateFareCommand, NotifyDriverCommand, and UpdateRideHistoryCommand.

We pass the concrete command objects to the ride request function, which stores them in a queue or list.

The ride request function then iterates over the list of commands and calls the execute method on each command object.

The execute method of each command object performs the specific task it's responsible for, such as finding a nearby driver or calculating the fare.

Once all the commands have been executed, the ride request function completes the ride request process.

// Define the Command interface
class Command {
  execute() {}
}

// Define concrete command classes
class FindDriverCommand extends Command {
  execute() {
    // Find a nearby driver
    console.log("Finding a nearby driver...");
  }
}

class CalculateFareCommand extends Command {
  execute() {
    // Calculate the fare
    console.log("Calculating the fare...");
  }
}

class NotifyDriverCommand extends Command {
  execute() {
    // Notify the driver about the ride request
    console.log("Notifying the driver...");
  }
}

class UpdateRideHistoryCommand extends Command {
  execute() {
    // Update the user's ride history
    console.log("Updating ride history...");
  }
}

// The ride request function that uses the Command Pattern
function requestRide() {
  const commands = [
    new FindDriverCommand(),
    new CalculateFareCommand(),
    new NotifyDriverCommand(),
    new UpdateRideHistoryCommand()
  ];

  // Execute the commands in sequence
  for (let i = 0; i < commands.length; i++) {
    commands[i].execute();
  }
}

// Request a ride
requestRide();
**Logs**
Finding a nearby driver...
Calculating the fare...
Notifying the driver...
Updating ride history...


Enter fullscreen mode Exit fullscreen mode

In this example, we define the Command interface and several concrete command classes that implement the interface. We then create a requestRide function that uses the Command Pattern to execute a sequence of commands, such as finding a nearby driver, calculating the fare, notifying the driver, and updating the user's ride history. Finally, we call the requestRide function to request a ride, which executes the commands in sequence.

Using the Command Pattern in this way provides several benefits, including:

Decoupling the client (the ride request function) from the implementation details of each task. This makes it easier to add new tasks or modify existing ones without affecting the client code.
Making it possible to queue or log commands for later processing, which can be useful for handling high traffic or distributed systems.
Allowing for undoable operations, which can be useful in scenarios where a user might accidentally request a ride or change their mind after the ride has been requested.

While the Command Pattern has several advantages, it also has some potential pitfalls:

Complexity: The Command Pattern can add extra complexity to the code by requiring the creation of additional classes and objects. This can make the code harder to read and understand, especially for junior developers.

Overhead: Using the Command Pattern can add overhead to the system, as each command needs to be created as an object and stored in memory. This can be a concern in resource-constrained environments.

Maintenance: As the number of commands and the complexity of the system grow, maintaining the Command Pattern can become challenging. Refactoring and debugging can be more difficult due to the increased number of classes and objects.

Unnecessary for simple tasks: The Command Pattern is designed to handle complex tasks that require a sequence of steps or undoable operations. For simple tasks, it can be overkill and add unnecessary complexity to the code.

Potential for misuse: The Command Pattern can be misused if developers create too many unnecessary commands or use it in situations where it is not needed, leading to code bloat and decreased performance.

It's important to carefully consider whether the Command Pattern is the right solution for a particular problem, and to use it judiciously when it is appropriate.

Top comments (0)