Ir ao conteúdo

Padrão de Projeto Command em PHP com exemplo

O Padrão de Projeto Command é, como demonstrado no diagrama de classe UML a seguir, nos dá uma boa ideia de que sua finalidade é encapsular invocações de objetos que representam alguma ação. Ou seja, conseguimos invocar comandos, até mesmo enfileirados, com diferentes operações, de modo que possamos desfazer algumas destas operações se necessário, ou até mesmo disparar um evento em um momento futuro.

Diagrama de Classe UML do Padrão de Projeto Command
Diagrama de Classe UML do Padrão de Projeto Command

O uso do Padrão Command nos permite desacoplar objetos que produzem os comandos de seus consumidores. Isso é uma boa prática de programação.

Um bom exemplo de uso do Padrão de Projeto Command são os comandos de ações de um editor de texto, você tem comandos como Copy, Cut,  Print, etc  Você pode criar uma interface para executar estes comandos e até mesmo criar um histórico dos comandos acionados.  Pode observer, literalmente temos, um objeto invocador (Invoker), um objeto que representa o comando (command) e um objeto que vai receber esse comando (Receiver)

Representação simplista do Command Pattern
Representação simplista do Command Pattern

Nada como um bom exemplo prático para você fixar melhor este conceito.

O problema que vamos resolver é relativo a manipular arquivos em diferentes sistemas operacionais, como Unix (Linux) e Windows. Uma coisa comum entre os sistemas é que quando vamos manipular algum tipo de arquivo, para fazermos uma escrita nele, temos algumas ações de execuções: abrir o arquivo, escrever no arquivo e quando tudo estiver pronto, temos que fechar o arquivo. Mesmo sendo através de comandos, todos esses passos precisam ser seguidos. Vamos ao exemplo! Já vamos definir o contrato de execução destes comandos:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command;

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

Se prepara que agora o padrão Command vai fazer total sentido quando você responder a seguinte pergunta: Quem vai recepcionar os comandos de abrir, escrever e fechar? É neste ponto que a maioria fica com dúvidas. O objeto Receiver, é como se fosse o alvo que vai receber nossos comandos. Então vamos definir a interface FileSystem para ser o nosso Receiver para implementar as ações:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command\Receiver;

interface FileSystem
{
    public function openFile(): void;
    public function writeFile(string $content): void;
    public function closeFile(): void;
}

Agora vamos definir as classes relacionadas a estes comandos. Vou aproveitar e mostrar um recurso novo do PHP 8.0, Class constructor property promotion. Você consegue “omitir” na escrita a declaração das propriedades e autodeclarar tipo e visibilidade no momento da chamada do construtor.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command;

use Growthdev\DesignPatterns\Behavioral\Command\Receiver\FileSystem;

final class OpenFileCommand implements Command
{
    private FileSystem $fileSystem;

    public function __construct(FileSystem $fileSystem)
    {
        $this->fileSystem = $fileSystem;
    }

    public function execute(): void
    {
        $this->fileSystem->openFile();
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command;

use Growthdev\DesignPatterns\Behavioral\Command\Receiver\FileSystem;

final class WriteFileCommand implements Command
{
    private FileSystem $fileSystem;

    public function __construct(
        FileSystem $fileSystem,
        private string $content
    ) {
        $this->fileSystem = $fileSystem;
    }

    public function execute(): void
    {
        $this->fileSystem->writeFile($this->content);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command;

use Growthdev\DesignPatterns\Behavioral\Command\Receiver\FileSystem;

final class CloseFileCommand implements Command
{
    private FileSystem $fileSystem;

    public function __construct(FileSystem $fileSystem)
    {
        $this->fileSystem = $fileSystem;
    }

    public function execute(): void
    {
        $this->fileSystem->closeFile();
    }
}

Agora vamos fazer as classes concretas para cada Sistema Operacional recepcionar os comandos sobre si.

Diagrama de Classe dos File Systems
Diagrama de Classe dos File Systems
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command\Receiver;

final class UnixFileSystem implements FileSystem
{
    private string $fileName = 'file.sh';

    public function openFile(): void
    {
        printf("Unix file %s is open", $this->fileName);
    }
    
    public function writeFile(string $content): void
    {
        printf("Unix file %s is writing of the contents: %s", $this->fileName, $content);
    }

    public function closeFile(): void
    {
        printf("Unix file %s is closed", $this->fileName);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command\Receiver;

final class WindowsFileSystem implements FileSystem
{
    private string $fileName = 'file.bat';

    public function openFile(): void
    {
        printf("Windows file %s is open", $this->fileName);
    }
    
    public function writeFile(string $content): void
    {
        printf("Windows file %s is writing of the contents: %s", $this->fileName, $content);
    }

    public function closeFile(): void
    {
        printf("Windows file %s is closed", $this->fileName);
    }
}

Agora precisamos só criar o invocador dos comandos(Invoker) para completar o padrão Command. Como tem bastante elementos, vou representar o Diagrama de Classe completo do padrão Command deste exemplo, para você comparar com o original e ver sua representatividade.

Diagrama de Classe UML com exemplo do Padrão de Projeto Command
Diagrama de Classe UML com exemplo do Padrão de Projeto Command
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Command;

final class FileInvoker
{
    private Command $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    public function execute(): void
    {
        $this->command->execute();
    }
}

Teste manipulando arquivos para o Sistema Windows

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\Command;

use Growthdev\DesignPatterns\Behavioral\Command\FileInvoker;
use Growthdev\DesignPatterns\Behavioral\Command\OpenFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\WriteFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\CloseFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\Receiver\WindowsFileSystem;

use PHPUnit\Framework\TestCase;

final class WindowsFileSystemCommandTest extends TestCase
{
    public function testCanExecuteOpenFileCommand(): void
    {
        $openFile = new OpenFileCommand(new WindowsFileSystem);
        
        $invoker = new FileInvoker($openFile);
        $invoker->execute();

        $this->expectOutputString("Windows file file.bat is open");
    }

    public function testCanExecuteWrittingFileCommand(): void
    {
        $writeFile = new WriteFileCommand(
            new WindowsFileSystem,
            'sh Windows command'
        );
        
        $invoker = new FileInvoker($writeFile);
        $invoker->execute();

        $this->expectOutputString(
            "Windows file file.bat is writing of the contents: sh Windows command"
        );
    }

    public function testCanExecuteCloseFileCommand(): void
    {
        $closeFile = new CloseFileCommand(new WindowsFileSystem);
        
        $invoker = new FileInvoker($closeFile);
        $invoker->execute();

        $this->expectOutputString("Windows file file.bat is closed");
    }
}
Teste do Padrão de Command para arquivos Windows
Teste do Padrão de Command para arquivos Windows

Teste manipulando arquivos para o Sistema Linux

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\Command;

use Growthdev\DesignPatterns\Behavioral\Command\FileInvoker;
use Growthdev\DesignPatterns\Behavioral\Command\OpenFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\WriteFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\CloseFileCommand;
use Growthdev\DesignPatterns\Behavioral\Command\Receiver\UnixFileSystem;
use PHPUnit\Framework\TestCase;

final class UnixFileSystemCommandTest extends TestCase
{
    public function testCanExecuteOpenFileCommand(): void
    {
        $openFile = new OpenFileCommand(new UnixFileSystem);
        
        $invoker = new FileInvoker($openFile);
        $invoker->execute();

        $this->expectOutputString("Unix file file.sh is open");
    }

    public function testCanExecuteWrittingFileCommand(): void
    {
        $writeFile = new WriteFileCommand(
            new UnixFileSystem,
            'sh unix command'
        );
        
        $invoker = new FileInvoker($writeFile);
        $invoker->execute();

        $this->expectOutputString(
            "Unix file file.sh is writing of the contents: sh unix command"
        );
    }

    public function testCanExecuteCloseFileCommand(): void
    {
        $closeFile = new CloseFileCommand(new UnixFileSystem);
        
        $invoker = new FileInvoker($closeFile);
        $invoker->execute();

        $this->expectOutputString("Unix file file.sh is closed");
    }
}
 Teste do Padrão de Command para arquivos Linux
Teste do Padrão de Command para arquivos Linux

Chegamos ao fim de mais um artigo sobre padrões de projetos. Todos os demais padrões do Livro GOF, você encontra no artigo Resumo dos Padrões de Projetos (Design Patterns).

Todos os códigos de todos os Padrões de Projetos, você encontra também no meu Github:

https://github.com/growthdev-repo/design-patterns

Agora preciso saber de você já tem ideia de onde vai utilizar o padrão Command nos seus projetos? Se já utilizou, deixa aqui nos comentários um resumo para que outros possam compartilhar das suas experiências. Isso ajuda bastante. E se você compartilhar este artigo, não estará só me ajudando, vai ajudar outros profissionais a evoluírem em suas carreiras como programadores também.

Até o próximo artigo!

Confiança Sempre!!!

Fontes:

Publicado emDesign PatternPadrões de ProjetosPHP

Seja o primeiro a comentar

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *