SOLID e o princípio do faça algo, mas do seu jeito

Fala devs, estou aqui mais uma vez falando sobre SOLID, já aproveito e deixo aqui o link do primeiro artigo onde falei sobre SOLID e o princípio do não faça mais do que não deve. E agora, já podemos iniciar os estudos no segundo princípio SOLID, o Open/Closed Principle (OCP), em português: Princípio do Aberto/Fechado.

Problema

E vamos utilizar uma nova história para entender a importância desse princípio em nosso desenvolvimento.

Digamos que você trabalha no setor Jurídico de determinada empresa, e você precisa de algumas informações do setor de TI, você fará essa solicitação na forma como esse setor aceita. Ocorre que você precisa de informações muito parecidas do setor de MKT, só que por algum motivo, a solicitação é feita de forma diferente.

Até então, estamos fazendo solicitações para dois setores diferentes, agora imaginem o problema que é, se precisarmos da mesma informação de outros 50 setores. Nós teríamos que nos adaptar a cada setor, porque a forma de solicitar é diferente, sendo que as informações para solicitar, e as respostas obtidas, são bem similares em todos os setores.

E para clarificar o problema, temos o seguinte caso: você trabalha em determinado sistema e quer mandar uma notificação para determinado usuário, seja via e-mail, sms ou wpp. O problema é que dependendo de qual classe de notificação escolhida, a chamada será diferente.

class Sms
{
    public function sendSMS(User $user): bool
    {
        // Lógica de envio de SMS
    }
}

class Email
{
    public function sendEmail(User $user): bool
    {
        // Lógica de envio de e-mail
    }
}

class WhatsApp
{
    public function sendWhatsApp(User $user): bool
    {
        // Lógica de envio de WhatsApp
    }
}

class UserNotification
{
    public function sendNotificationSms(Sms $sms)
    {
        $user = User::find(1);
        $sms->sendSMS($user);
    }

    public function sendNotificationEmail(Email $email)
    {
        $user = User::find(1);
        $email->sendEmail($user);
    }

    public function sendWhatsApp(WhatsApp $whatsApp)
    {
        $user = User::find(1);
        $whatsApp->sendWhatsApp($user);
    }
}

Então, temos uma classe que vai receber o tipo de notificação, fará uma busca por usuário, e chamará a ação de notificar, passando o usuário buscado. Mas vejam só que cada notificação é feita de uma maneira diferente, a chamada da ação é diferente para cada tipo de notificação, os dados enviados e recebidos, são os mesmos.

Caso adicionarmos mais algum tipo de notificação, teríamos que modificar nossa classe UserNotification para criar um método que se adapte a escolha desse tipo de notificação, e isso é um problema. Não só por modificar o código desnecessariamente, como também por adicionar uma responsabilidade desnecessária, lembram do SRP?

Princípio do Aberto/Fechado

O Princípio do Aberto/Fechado do SOLID, nos diz o seguinte:

Um artefato de software deve ser aberto para extensão, mas fechado para modificação.

Em resumo, o que esse princípio quer dizer é o seguinte: temos uma ação em nosso sistema, que terá diferentes comportamentos para uma mesma entrada e saída, além disso, o comportamento de determinada ação, não afeta/modifica o comportamento de outra ação. Assim, temos ações que são abertas para extensão (novas implementações), porém, uma implementação não depende ou afeta outra implementação, ou seja, uma ação fechada para modificação.

Então, voltando ao nosso problema, nós sabemos quais são os dados enviados, quais são os dados retornados, só não sabemos o processamento que é feito internamente, e esse processamento não afeta outro. É disso que esse princípio trata, e caso vocês ainda não tenham absorvido a ideia, estamos falando fortemente sobre Polimorfismo. Nós temos uma ação padrão em determinadas classes, mas a implementação delas divergem uma das outras.

Voltando para o nosso código, vamos refatorar aplicando o OCP.

interface Notification
{
    public function send(User $user): bool;
}

class Sms implements Notification
{
    public function send(User $user): bool
    {
        // Lógica de envio de SMS
    }
}

class Email implements Notification
{
    public function send(User $user): bool
    {
        // Lógica de envio de e-mail
    }
}

class WhatsApp implements Notification
{
    public function send(User $user): bool
    {
        // Lógica de envio de WhatsApp
    }
}

class UserNotification
{
    public function send(Notification $notification): bool
    {
        $user = User::find(1);
        $notification->send($user);
    }
}

Fizemos uso de uma interface para definir a base dessas implementações, ou seja, é como se definíssemos um molde, onde quem implementar essa interface deverá fazer a implementação desse método específico, e o seu comportamento, não irá alterar outros comportamentos e nem sofrerá alteração.

Detalhando os ganhos que tivemos com a refatoração: aplicamos o SRP na nossa classe de UserNotification e agora não precisamos modificá-la toda vida que um novo tipo de notificação for adicionado, podemos facilmente substituir nossa notificação por outra sem que isso afete outras partes do código.

Resumo

Esse princípio talvez não seja o mais fácil de ser entendido, mas quero que lembrem-se: sabemos as entradas e saídas, logo, podemos definir algo que possa ter múltiplas implementações, porém, uma nova implementação, não afeta a anterior.

Espero que vocês tenham entendido o problema que esse princípio busca resolver, como ele é fortemente associado ao Polimorfismo, e como ele nos ajuda a trocar classes sem modificar nosso código. Por hoje é só e até o próximo artigo.

15