Orientação a objetos: herança e polimorfismo

Continuação do post sobre orientação a objetos. Agora vamos ver os dois conceitos que fecham o quarteto: herança e polimorfismo.


No post anterior a gente viu encapsulamento e abstração, os dois primeiros conceitos da orientação a objetos. Se você ainda não leu, vale dar uma passada por lá antes de continuar aqui.

Agora vamos para os dois conceitos que fecham o quarteto: herança e polimorfismo. Eles são um pouco mais avançados, mas seguem a mesma lógica prática dos anteriores.

Herança

O nome já diz bastante. Herança é quando uma classe herda as propriedades e os métodos de outra classe.

Vamos usar o exemplo do funcionário que a gente criou antes. Toda empresa tem tipos diferentes de funcionário. Um programador é um funcionário, um gerente é um funcionário, um vendedor é um funcionário. Todos eles têm nome, salário base, horas extras. Mas cada um também tem coisas específicas da sua função.

Sem herança, você criaria uma classe separada para cada tipo e repetiria as mesmas propriedades em todas elas:

class Programador {
    public $nome;
    public $salarioBase;
    public $horasExtras;
    public $linguagem; // específico do programador
}
 
class Gerente {
    public $nome;
    public $salarioBase;
    public $horasExtras;
    public $numeroDeFuncionarios; // específico do gerente
}

Voltou o problema do copy e paste. Se amanhã você precisar adicionar uma nova propriedade comum a todos os funcionários, vai ter que adicionar em cada classe separadamente.

Com herança você cria uma classe base com o que é comum, e as outras classes herdam dela:

class Funcionario {
    public $nome;
    public $salarioBase;
    public $horasExtras;
    public $valorDaHora;
 
    public function getSalarioEfetivo() {
        return $this->salarioBase + ($this->horasExtras * $this->valorDaHora);
    }
}
 
class Programador extends Funcionario {
    public $linguagem;
}
 
class Gerente extends Funcionario {
    public $numeroDeFuncionarios;
}

O Programador e o Gerente herdam automaticamente tudo que está na classe Funcionario. Você não precisa repetir nada. E se precisar adicionar uma nova propriedade em todos os funcionários, adiciona só na classe base e todos os filhos recebem automaticamente.

Na prática fica assim:

$programador = new Programador();
$programador->nome = "Fulano";
$programador->salarioBase = 5000;
$programador->horasExtras = 8;
$programador->valorDaHora = 50;
$programador->linguagem = "PHP";
 
echo $programador->getSalarioEfetivo(); // 5400

O Programador tem acesso ao método getSalarioEfetivo mesmo sem ter definido ele, porque herdou de Funcionario.

Polimorfismo

Polimorfismo significa "muitas formas". Na prática significa que você pode ter o mesmo método em classes diferentes, e cada classe vai executar esse método do seu próprio jeito.

Continuando o exemplo: o cálculo de salário efetivo pode ser diferente para cada tipo de funcionário. Um gerente recebe uma bonificação extra. Um vendedor recebe comissão. A conta de cada um é diferente, mas todos têm um método chamado getSalarioEfetivo.

class Funcionario {
    public $nome;
    public $salarioBase;
    public $horasExtras;
    public $valorDaHora;
 
    public function getSalarioEfetivo() {
        return $this->salarioBase + ($this->horasExtras * $this->valorDaHora);
    }
}
 
class Gerente extends Funcionario {
    public $bonificacao;
 
    public function getSalarioEfetivo() {
        $salarioBase = parent::getSalarioEfetivo();
        return $salarioBase + $this->bonificacao;
    }
}
 
class Vendedor extends Funcionario {
    public $comissao;
 
    public function getSalarioEfetivo() {
        $salarioBase = parent::getSalarioEfetivo();
        return $salarioBase + $this->comissao;
    }
}

O Gerente e o Vendedor sobrescrevem o método getSalarioEfetivo com a lógica específica deles. O parent::getSalarioEfetivo() chama o método da classe pai, que é o cálculo base, e cada classe adiciona o que é específico dela por cima.

A grande sacada do polimorfismo aparece quando você tem uma lista de funcionários de tipos diferentes e precisa calcular o salário de todos:

$funcionarios = [
    new Programador(),
    new Gerente(),
    new Vendedor(),
];
 
foreach ($funcionarios as $funcionario) {
    echo $funcionario->getSalarioEfetivo();
}

O código que percorre a lista não precisa saber se está lidando com um programador, um gerente ou um vendedor. Ele simplesmente chama getSalarioEfetivo em cada um e cada objeto responde do seu próprio jeito. Mesma chamada, comportamentos diferentes. Isso é polimorfismo.

Por que isso tudo importa

Esses quatro conceitos, encapsulamento, abstração, herança e polimorfismo, existem para resolver o mesmo problema: organizar o código de um sistema grande de forma que ele seja fácil de manter e de expandir.

Sem eles, quando o sistema cresce, o código vira uma bagunça difícil de mexer. Com eles, você consegue adicionar novos tipos de funcionário sem tocar nas partes que já existem, mudar a regra de cálculo de salário em um lugar só, e garantir que o sistema inteiro se comporta de forma consistente.

A orientação a objetos não é receita de bolo para qualquer situação. Mas quando o sistema tem muitas entidades, muitas regras de negócio e muitos lugares reutilizando as mesmas lógicas, ela é a forma mais organizada de estruturar tudo isso.