vendredi 29 décembre 2023

How to use the Command Pattern when dealing with different parameters

Having a look at the UML diagrams for the Command Pattern reported here and here, the class of the invoker object (the one that inkoves the execute() method of a command object) must provide a setCommand() method to allow the client code to set the proper command object on the invoker: but how can such method be used when dealing with command objects that have different parameters which, additionally, are known only at runtime? I'll try to make an example.

class Messenger
{
  public function sendEmail(string $email, string $subject, string $body): void { ... }
  public function sendSms(string $phone, string $text): void { ... }
}

interface Command
{
  public function execute(): void;
}

class SendEmailCommand implements Command
{
  private readonly Messenger $messenger;
  private readonly string $email;
  private readonly string $subject;
  private readonly string $body;

  public function __constructor(Messenger $messenger)
  {
    $this->messenger = $messenger;
  }

  public function setEmail(string $email): void { $this->email = $email; }
  public function setSubject(string $subject): void { $this->subject = $subject; }
  public function setBody(string $body): void { $this->body = $body; }

  public function execute(): void
  {
    $this->messenger->sendEmail($this->email, $this->subject, $this->body);
  }
}

class SendSmsCommand implements Command
{
  private readonly Messenger $messenger;
  private readonly string $phone;
  private readonly string $text;

  public function __constructor(Messenger $messenger)
  {
    $this->messenger = $messenger;
  }

  public function setPhone(string $phone): void { $this->phone = $phone; }
  public function setText(string $text): void { $this->text = $text; }

  public function execute(): void
  {
    $this->messenger->sendSms($this->phone, $this->text);
  }
}

In such a scenario how much is it useful to have a setCommand(Command $command) on the invoker class? In some languages I would even get an error later when trying to call setX() because the Command interface doesn't define them. Would it be a bad idea to do something like this?

class EmailSendButton()
{
  private readonly Messenger $messenger;

  public function __constructor(Messenger $messenger)
  {
    $this->messenger = $messenger;
  }

  public function onClick(): void
  {
    $email = $this->getEmail();
    $subject = $this->getSubject();
    $body = $this->getBody();

    $sendEmailCommand = new SendEmailCommand($this->messenger);
    $sendEmailCommand->setEmail($email);
    $sendEmailCommand->setSubject($subject);
    $sendEmailCommand->setBody($body);
    $sendEmailCommand->execute();
  }
}

I know it is not ideal to instantiate a class inside another class, it creates coupling, but how would you solve it?

I was thinking about setCommand(SendEmailCommand $sendEmailCommand) instead of the easier setCommand(Command $command) but it adds more complexity on the client code than benefits IMO.

Aucun commentaire:

Enregistrer un commentaire