DEV Community

Vinicius Dias for PHP Rio

Posted on • Updated on • Originally published at dias.dev

Programação procedural orientada a classes

Farsa da programação Orientada a Objetos

Acredito que todos concordamos que a programação Orientada a Objetos torna nosso código mais legível e permite arquiteturas mais interessantes do que a programação procedural, além de permitir recursos interessantes de reutilização de código.

Partindo desse pressuposto, também acredito que seja intenção de qualquer pessoa que trabalhe com desenvolvimento aprender mais e mais as técnicas e boas práticas deste paradigma de programação.

O problema acontece quando a gente vem de um aprendizado de programação procedural e tenta aplicar as mesmas técnicas fazendo uso de classes e objetos.

Exemplo clássico

Há um exemplo muito clássico de um código que claramente, embora utilize classes e objetos, é procedural:

Definição de uma classe

<?php

class Pessoa
{
    private string $primeiroNome;
    private string $ultimoNome;
    private \DateTimeInterface $dataNascimento;

    public function setPrimeiroNome(string $primeiroNome): void
    {
        $this->primeiroNome = $primeiroNome;
    }

    public function getPrimeiroNome(): string
    {
        return $this->primeiroNome;
    }

    public function setUltimoNome(string $ultimoNome): void
    {
        $this->ultimoNome = $ultimoNome;
    }

    public function getUltimoNome(): string
    {
        return $this->ultimoNome;
    }

    public function setDataNascimento(\DateTimeInterface $dataNascimento): void
    {
        $this->dataNascimento = $dataNascimento;
    }

    public function getDataNascimento(): \DateTimeInterface
    {
        return $this->dataNascimento;
    }
}

Enter fullscreen mode Exit fullscreen mode

Utilização dela

$pessoa = new Pessoa();
$pessoa->setPrimeiroNome('Vinicius');
$pessoa->setUltimoNome('Dias');
$pessoa->setDataNascimento(new \DateTimeImmutable('1997-10-15')); // aceito presentes

$idade = $pessoa->getDataNascimento()->diff(new \DateImmutable())->y;

echo "{$pessoa->getPrimeiroNome()} {$pessoa->getUltimoNome()} tem $idade anos";
Enter fullscreen mode Exit fullscreen mode

Problema

Olhando esse simples exemplo:

  1. Você nota algum problema?
  2. Identifica semelhança com algum pedaço de código que você tenha escrito recentemente?

Depois de você refletir um pouco, vamos continuar.

O código exemplificado utiliza uma classe Pessoa e a partir desta classe, cria um objeto. Logo, está utilizando do paradigma de programação orientada a objetos, certo? Errado!

Esse exemplo é claramente um código procedural que usa uma classe. Nada além disso.

Proposta

Se você possui uma classe, os objetos gerados através dela devem ter seus comportamentos descritos (através de métodos) e não apenas fornecer os dados para realizar essas ações.

O código anterior poderia ser escrito da seguinte forma para usar o paradigma Orientado a Objetos:

<?php

class Pessoa
{
    private string $primeiroNome;
    private string $ultimoNome;
    private \DateTimeInterface $dataNascimento;

    public function __construct(
        string $primeiroNome,
        string $segundoNome,
        \DateTimeInterface $dataNascimento
    ) {
        $this->primeiroNome = $primeiroNome;
        $this->ultimoNome = $ultimoNome;
        $this->dataNascimento = $dataNascimento;
    }

    public function nomeCompleto(): string
    {
        return "{$this->primeiroNome} {$this->ultimoNome}";
    }

    public function idade(): int
    {
        $hoje = new DateTimeImmutable();

        return $this->dataNascimento->diff($hoje)->y;
    }
Enter fullscreen mode Exit fullscreen mode

E assim poderíamos utilizar esta classe:

<?php

$pessoa = new Pessoa('Vinicius', 'Dias', new \DateTimeImmutable('1997-10-15'));

echo "{$pessoa->nomeCompleto()} tem {$pessoa->idade()} anos.";
Enter fullscreen mode Exit fullscreen mode

Motivação da mudança

Com essa simples modificação nós não temos mais instâncias inválidas, já que agora toda Pessoa tem seu primeiro e último nome e sua data de nascimento. Antes, primeiro nós estávamos criando uma Pessoa em um estado inconsistente, sem seus dados, para depois defini-los.

Além disso, agora qualquer método que receba uma Pessoa por parâmetro não precisa saber se nesse objeto o nome completo está separado em primeiro e segundo nome ou não. Não precisa saber como o cálculo da idade é feito.

Caso a forma de apresentar o nome precise ser modificado (de "Vinicius Dias" para "Dias, Vinicius", por exemplo), podemos ir direto no ponto correto: O método nomeCompleto.

Embora seja um exemplo muito simples, ele mostra com clareza como normalmente escrevemos código acreditando estar utilizando a programação orientada a objetos.

Caso você tenha algum outro exemplo desse tipo em mente, já tenha visto algum código procedural com classes assim, ou tenha alguma outra sugestão de melhoria pra esse exemplo, que tal compartilhar com a gente aqui nos comentários?

Princípios e padrões

Para nos ajudar a utilizar de forma mais "correta" a orientação a objetos, existem diversos padrões e princípios. Uns mais simples como o Tell, Don't ask, outros mais completos e complexos como os princípios SOLID.

Todos os princípios merecem uma atenção especial, porém isso foge do escopo deste artigo, mas uma rápida busca te mostra uma imensidão de conteúdo a respeito.

Conclusão

Muito frequentemente nós utilizamos o paradigma procedural em nosso código e só pelo fato de termos classes definidas no código, achamos que estamos utilizando a Orientação a Objetos.

Este paradigma é muito mais do que apenas definir classes, atributos e métodos, logo, precisa de muito mais estudo do que apenas a leitura da documentação da sua linguagem favorita para aprender a sintaxe.

Top comments (11)

Collapse
 
raphaeldasilva profile image
Raphael da Silva • Edited

Eu passei por algumas fases no meio do processo de tentar entender programação orientada a objetos. Escrevi um post sobre isso. No começo a gente acha que está usando objetos de verdade e mal sabe a profundidade dos assuntos.

Collapse
 
mhgontijo profile image
Matheus Gontijo

Excelente reflexão, Vinícius. Obrigado por compartilhar conhecimento ;- )

Curioso em ~ouvir~ ler você sobre: Se a classe Pessoa tivesse +40 atributos, você não acha que o __construct ficaria longo demais?

Collapse
 
cviniciussdias profile image
Vinicius Dias • Edited

Opa, obrigado demais pelo feedback.

Existe uma "regra" no conceito de Object Calisthenics que diz que um método não deve possuir mais de 2 parâmetros.

Embora eu acredite ser um exagero, a sugestão de solução poderia ser aplicada a esse caso.

Muito provavelmente desses 40 atributos, vários deles seriam relacionados e passíveis de extração para uma nova classe.

Exemplo simples. Antes:

class Pessoa
{
    public function __construct(string $primeiroNome, string $ultimoNome, string $dddTelefone, string $numeroTelefone)
    {
        // ...
    }
}

Depois:

class Pessoa
{
    public function __construct(Nome $nome, Telefone $telefone)
    {
        // ...
    }
}

Assim ganhamos ainda mais em coesão, tirando possíveis responsabilidades de validação desses atributos da classe Pessoa.

Entendeu a ideia? O que acha?

Collapse
 
hilderjares profile image
Hilderjares(sid)

Penso eu que quando uma classe chega a ter esse tando de atributos, ela foi mal pensanda; é uma classe que tem muitas responsabilidades.

Thread Thread
 
cviniciussdias profile image
Vinicius Dias

Muito provavelmente, sim!

Thread Thread
 
mhgontijo profile image
Matheus Gontijo

Uhmmm sim! Até aqui todos concordamos plenamente ;- ) Quando o assunto são classes de negócios, +40 atributos é algo altamente doloroso. É muita "gente" envolvida para a mesma classe. Exemplo: EnvieEmailDeAniversario. Quebra SRP completamente.

O lance "desafiador" aqui é a ampla interpretação do problema... O que gostaria de saber é em relação a classes de "entidades"... Veja Pessoa, Corretor, Jogador, Cliente. Obviamente, podemos extrair e distribuir os atributos em diferentes responsabilidades. No entanto, é possível que mesmo após uma boa distribuição de responsabilidades, e um cuidado imenso em quais responsabilidades cada classe deve ter, tenhamos classes com muitos atributos. Vamos dizer que de ~60 atributos requisitados consigamos enxugar para 25, em algumas outras entidades. Ainda sim temos um número considerável de atributos no __construct. Essa foi a ideia inicial.

O que acham? Gostaria de ouvir vocês.

Thread Thread
 
cviniciussdias profile image
Vinicius Dias

Entendi seu ponto, mas continuaria sendo um caso de má abstração uma classe ter esse número de atributos.

Você consegue compartilhar um gist com um exemplo real pra poder analisar um ponto mais concreto?

:-D

Thread Thread
 
mhgontijo profile image
Matheus Gontijo • Edited

Opa, tem como sim! Obrigado pela resposta.

Poderíamos ter uma discussão altamente filosófica por mais de 30 anos a fim de chegar a um senso comum se "um ou outro" atributo deveria de fato pertencer a classe Pedido ou não. No entanto, com o intuito de tornar o exemplo mais fácil de ser compreendido, poderíamos assumir que esses atributos tem minimamente alguma relação com a classe Pedido?

Eu listei rapidamente 24 atributos que me vieram a mente. Por outro lado, eu acredito que uma grande empresa com 500/1.000/10.000 colaboradores, possuidora de um processo rico em detalhes e com abundantes regras de negócio poderia "facilmente" incrementar, pelo menos, uns 10 atributos na lista seguinte.

Segue a lista: pastebin.com/raw/8G4HxsE1

Alguma sugestão de abstração? Como podemos diluir esses 24 atributos em outras responsabilidades? Podemos chegar em 2-4 dependências?

Muito obrigado por seu tempo e pela thread, Vinicius!!!!!

Thread Thread
 
cviniciussdias profile image
Vinicius Dias • Edited

Eu que agradeço pela thread. Levanta pontos bem interessantes.

Então, concordo que todos os atributos possuem relação com a classe, mas isso não muda o fato de que ela pode estar mal abstraída. Eu preciso partir de algumas premissas pra poder pensar em algumas soluções... Só um expert de negócios poderia confirmar essas minhas premissas, mas vamos a alguns "chutes". hehehe

Data de criação e atualização são informações de persistência, não de domínio, logo não entram na entidade. São "atributos virutais", como alguns ORMs chamam.

Loja muito provavelmente seria o Aggregate Root que tornaria o acesso a Pedido possível, logo seria redundante ter esse atributo em Pedido.

  • Pontos cartão de presente
  • Pontos fidelidade
  • Disclaimers aceitos
  • Papel de presente ou embrulho
  • Retirada na loja
  • Arquivos anexados

Todos esses acima, se entendi corretamente o conceito do domínio, se enquadram em DetalhesPedido ou algo do tipo, mas pode ser mais discutido, claro. Posso ter entendido errado.

Subtotal e total são valores calculados baseando-se nos produtos. Uma estrutura de Composite poderia ser aplicada até para ter "pedidos aninhados".

Pedido pode significar muita coisa dependendo do domínio. Às vezes pode ter uma Compra que possui vários pedidos. Às vezes um cliente pode realizar apenas um pedido por data. Se esses forem os casos, o Aggregate Root muda, retirando alguns outros atributos da classe.

Aí teriamos que entrar numa discussão bem mais aprofundada sobre o caso.

Mas ainda assim, se existe uma classe com antos atributos, chances são grandes de nem todos serem obrigatórios. Aí, a criação fica completa, o que normalmente pede uma espécie de Creation Method (que o DDD chama de Factory), Builder ou algo do tipo.

:-D

Thread Thread
 
mhgontijo profile image
Matheus Gontijo

Insights massa, top!!! 👍👍👍👍👍👍👍

Thread Thread
 
cviniciussdias profile image
Vinicius Dias

Obrigado pela discussão. Realmente me fez refletir aqui sobre algumas decisões que tomamos corriqueiramente.