Back-End

25 abr, 2013

Princípios de Projeto Orientado a Objetos – SRP (Single Responsibility Principle) – Parte 1

Publicidade

Quem trabalha com programação orientada a objetos, seja em PHP, Java ou qualquer outra linguagem OO, certamente já ouviu falar em padrões de projeto (Design Patterns). Existem vários padrões para solucionar diversos problemas em diferentes cenários. Você deve estar se perguntando: “Tá, e daí!? Isso eu já sabia!”. Ok, ok… Então, vamos direto ao assunto.

Para um melhor entendimento sobre padrões de projeto, é necessário ter um sólido conhecimento sobre Orientação e Objetos, e a essência pra quem deseja desenvolver uma aplicação Orientada a Objetos é conhecer os Princípios de Projeto Orientado a Objetos. A base para criação de diversos Padrões de Projeto, princípios básicos que devemos seguir para termos uma aplicação robusta, organizada, flexível e reutilizável.

SRP, que são eles:

  • SRP – Single reponsibility principle – Princípio da Responsabilidade Única;
  • OCP – Open/closed principle – Princípio do Aberto e Fechado;
  • LSP – Liskov substitution principle – Princípio da Substituição de Liskov;
  • ISP – Interface segregation principle – Princípio da Segregação de Interface;
  • DIP – Dependency inversion principle – Princípio da Inversão de Dependência;

SPR – Single responbilility principle – Princípio da Responsabilidade Única

  • Uma classe deve ter uma e apenas uma responsabilidade OU
  • Uma classe deve ter apenas um motivo para mudar.

Vamos supor que lhe foi solicitado a criação de um componente que deverá enviar um Cartão de Feliz Aniversário a todos os clientes aniversariantes do mês; pra isso, a ferramenta de CRM da empresa disponibiliza os aniversariantes do mês em um documento XML a partir da url: http://crm.empresa.com.br/aniversariantes.xml

Modelo do XML

<?xml version="1.0" encoding="UTF-8"?>
<ListaAniversariantes>
    <aniversariante>
        <nome>José Antonio Silva</nome>
        <email>jose.antonio@dominio.com.br</email>
    </aniversariante>
    <aniversariante>
		...
    </aniversariante>
</ListaAniversariantes>

Já possuindo as informações necessárias para desenvolver nosso componente, vamos elaborar o primeiro modelo preliminar.

Modelo preliminar 1

diagrama1

Classes Client, BirthdayCard e TriggerCards

<?php

class Client
{
    private $name;
    private $email;

    public function __construct($name, $email)
    {
        $this->name  = $name;
        $this->email = $email;
    }

    public function getName()
    {
        return $this->name;
    }

    // Métodos gets e sets
}

class BirthdayCard
{
    /**
     * Esse método retornada a conteudo com sua representação
     * gráfica do cartão com o nome do cliente
     *
     * @param string $name
     * @return string
     */
    public function getContentCard(Client $client)
    {
        return "Feliz Aniversário '{$client->getName()}'!";
    }
}

class TriggerCards
{
    private $clients = array();
    private $card;

    public function __construct(BirthdayCard $card)
    {
        $this->card = $card;
        $this->loadClients();
    }

    /**
     * Envia o cartão para o e-mail do cliente
     */
    public function sendCards()
    {
        foreach ($this->getClients() as $client)
        {
            $contentCard = $this->card->getContentCard($client);

            // Aqui ira a implementação para envio do e-mail
            // utilizando o conteúdo do BirthdayCard
            echo $contentCard . "\n";
        }
    }

    /**
     * Carrega os dados dos cliente aniversariantes
     */
    public function loadClients()
    {
        $xml = simplexml_load_file('http://crm.empresa1.com.br/aniversariantes.xml');

        foreach ($xml->aniversariante as $aniversariante)
        {
            $client = new Client((string) $aniversariante->nome, (string) $aniversariante->email);
            array_push($this->clients, $client);
        }
    }

    public function getClients()
    {
        return $this->clients;
    }
}

Bom, criamos uma classe Client para representar o cliente, criamos a classe BirthdayCard para representar o cartão de aniversário e por fim a classe TriggerCards, que irá disparar os cartões por e-mail. Nosso componente já estaria teoricamente pronto para ser colocado em produção, afinal, ele já envia o cartão de aniversário perfeitamente para o cliente aniversariante, mas vamos analisar com calma.

Analisando nossas classes, utilizando a definição do SRP, que diz que uma classe deve ter apenas uma responsabilidade, podemos enxergar claramente que a nossa classe TriggerCards está sendo responsável por obter os clientes aniversariantes do arquivo XML pelo método loadClients() e está responsável em enviar os cartões por e-mail pelo método sendCard()!

Acabamos de identificar a violação do princípio SRP! Perceba que a classe TriggerCards possui mais que uma responsabilidade. Vamos imaginar a adição do seguinte requisito ao componente.

Foi solicitado também que o componente criado possa obter os dados no formato JSON que será disponibilizado por uma empresa parceira e que utilizará o mesmo componente.

E agora? Como vamos poder reutilizar esse componente adicionando essa nova funcionalidade com esse modelo que fizemos? Ou seja, obter os clientes a partir de um JSON? Essa classe está violando também outro principio, o OCP, que será abordado no próximo artigo.

Bom, vamos remodelar essa solução aplicando o princípio SRP, tornando a classe TriggerCards com uma única responsabilidade e adicionar a leitura de clientes a partir de um documento JSON.

Modelo preliminar 2

diagrama2

Classes TriggerCards , LoadClientsAbstract, LoadClientsXml e LoadClientsJSON 

<?php

class Client
{
    private $name;
    private $email;

    public function __construct($name, $email)
    {
        $this->name  = $name;
        $this->email = $email;
    }

    public function getName()
    {
        return $this->name;
    }

    // Métodos gets e sets
}

class BirthdayCard
{
    /**
     * Esse método retornada a conteudo com sua representação
     * gráfica do cartão com o nome do cliente
     *
     * @param string $name
     * @return string
     */
    public function getContentCard(Client $client)
    {
        return "Feliz Aniversário '{$client->getName()}'!";
    }
}

class TriggerCards
{
    private $loadClients;
    private $card;

    public function __construct(BirthdayCard $card, LoadClientsAbstract $loadClients)
    {
        $this->card        = $card;
        $this->loadClients = $loadClients;
    }

    /**
     * Envia o cartão para o e-mail do cliente
     */
    public function sendCards()
    {
        foreach ($this->loadClients->getClients() as $client)
        {
            $contentCard = $this->card->getContentCard($client);

            // Aqui ira a implementação para envio do e-mail
            // utilizando o conteúdo do BirthdayCard
            echo $contentCard . "\n";
        }
    }
}

abstract class LoadClientsAbstract
{
    protected $clients = array();

    abstract protected function loadClients();

    public function __construct()
    {
        $this->loadClients();
    }

    public function getClients()
    {
        return $this->clients;
    }
}

class LoadClientsXML extends LoadClientsAbstract
{
    public function loadClients()
    {
        $xml = simplexml_load_file('http://crm.empresa1.com.br/aniversariantes.xml');

        foreach ($xml->aniversariante as $aniversariante)
        {
            $client = new Client((string) $aniversariante->nome, (string) $aniversariante->email);
            array_push($this->clients, $client);
        }
    }
}

class LoadClientsJSON extends LoadClientsAbstract
{
    public function loadClients()
    {
        // Implementação para leitura do arquivo JSON
    }
}

Veja que para solucionar o problema da classe TriggerCards criamos uma classe abstrata LoadClientsAbstract, que será referência para criarmos as classes concretas LoadClientsXML, LoadClientsJSON ou qualquer outro formato que possa vir a ser adicionado. Refatoramos também a classe TriggersCards, deixando-a com uma única responsabilidade, que é enviar os cartões por e-mail, tornando assim nosso componente flexível e reutilizável seguindo o princípio SRP.

Espero que tenha ficado claro como identificar e como aplicar o princípio SRP. No próximo artigo, irei falar sobre o princípio OCP – Open/closed principle.

Referências