Classes abstratas são uma poderosa ferramenta de orientação a objetos que permitem definir uma estrutura base para outras classes, sem que elas próprias possam ser instanciadas. Em PHP, uma classe abstrata serve como um modelo que declara métodos que devem ser implementados pelas classes filhas, garantindo um contrato mínimo de funcionalidade. Isso promove a reutilização de código e a consistência em hierarquias de classes.

Nesta aula, abordaremos a palavra-chave abstract, como declarar métodos abstratos, quando faz sentido utilizar essa abordagem e exemplos completos que ilustram a aplicação prática. Ao final, você terá clareza sobre como estruturar sistemas que exigem polimorfismo e padronização.

abstract

A palavra-chave abstract é usada para definir uma classe ou método como abstrato. Uma classe abstrata não pode ser instanciada diretamente; ela apenas pode ser estendida por outras classes. Classes abstratas podem conter métodos concretos (com implementação) e métodos abstratos (sem implementação). A declaração é feita colocando abstract antes da palavra class ou do método.

Exemplo de declaração de classe abstrata:

<?php
abstract class Animal {
    protected $nome;

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

    // Método concreto
    public function getNome() {
        return $this->nome;
    }

    // Método abstrato
    abstract public function fazerSom();
}
?>

Note que a classe Animal é abstrata e não pode ser instanciada. Ela possui um construtor concreto e um método abstrato fazerSom(). Qualquer classe que estenda Animal deve implementar fazerSom().

Métodos abstratos

Métodos abstratos são declarados sem corpo, apenas com a assinatura. Eles forçam as subclasses a fornecerem sua própria implementação. Um método abstrato não pode ser privado (private), pois precisa ser visível para a classe filha. Geralmente são declarados como protected ou public. Além disso, classes que contenham pelo menos um método abstrato devem ser declaradas como abstratas.

Exemplo de método abstrato:

<?php
abstract class Forma {
    abstract public function calcularArea();
}

class Circulo extends Forma {
    private $raio;

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

    public function calcularArea() {
        return pi() * $this->raio * $this->raio;
    }
}

class Retangulo extends Forma {
    private $largura;
    private $altura;

    public function __construct($largura, $altura) {
        $this->largura = $largura;
        $this->altura = $altura;
    }

    public function calcularArea() {
        return $this->largura * $this->altura;
    }
}
?>

Neste exemplo, a classe abstrata Forma define um método abstrato calcularArea(). As classes Circulo e Retangulo implementam esse método de acordo com suas próprias regras. Isso permite que o código que utiliza essas classes trate todas as formas de maneira polimórfica.

Quando usar

Classes abstratas são ideais quando você tem uma hierarquia de classes que compartilham comportamento comum, mas onde certas funcionalidades precisam ser implementadas de forma específica por cada subclasse. Elas são úteis para definir uma interface parcial, combinando métodos concretos (reutilizados) com métodos abstratos (obrigatórios).

Situações comuns incluem:

  • Modelagem de conceitos genéricos como "Animal", "Veículo" ou "Funcionário", onde cada tipo tem uma ação específica (ex.: fazer som, mover, calcular salário).
  • Fornecer uma implementação base para métodos que são comuns a todas as subclasses, como getters e setters.
  • Definir um contrato que garanta que todas as subclasses implementem determinados métodos, semelhante a interfaces, mas com a possibilidade de adicionar lógica compartilhada.

Diferenças entre classe abstrata e interface: enquanto interfaces só podem declarar métodos (sem implementação) e constantes, classes abstratas podem conter propriedades, métodos concretos e construtores. Use classe abstrata quando houver compartilhamento de código; use interface quando quiser apenas definir um contrato sem implementação.

Exemplos

Vamos a um exemplo mais completo que simula um sistema de pagamento. Temos uma classe abstrata Pagamento que define o esqueleto para diferentes formas de pagamento.

<?php
abstract class Pagamento {
    protected $valor;
    protected $status;

    public function __construct($valor) {
        $this->valor = $valor;
        $this->status = 'pendente';
    }

    public function getValor() {
        return $this->valor;
    }

    public function getStatus() {
        return $this->status;
    }

    abstract public function processar();

    abstract public function getTaxa();
}

class PagamentoCartao extends Pagamento {
    private $numeroCartao;

    public function __construct($valor, $numeroCartao) {
        parent::__construct($valor);
        $this->numeroCartao = $numeroCartao;
    }

    public function processar() {
        // Lógica de processamento com cartão
        $this->status = 'aprovado';
        echo "Pagamento de R$ {$this->valor} com cartão {$this->numeroCartao} processado.\n";
    }

    public function getTaxa() {
        return $this->valor * 0.03; // 3% de taxa
    }
}

class PagamentoBoleto extends Pagamento {
    private $codigoBarras;

    public function __construct($valor, $codigoBarras) {
        parent::__construct($valor);
        $this->codigoBarras = $codigoBarras;
    }

    public function processar() {
        // Lógica de processamento de boleto
        $this->status = 'aguardando_pagamento';
        echo "Boleto de R$ {$this->valor} gerado com código {$this->codigoBarras}.\n";
    }

    public function getTaxa() {
        return 2.50; // taxa fixa
    }
}

// Uso
$pagamentos = [
    new PagamentoCartao(150.00, '1234-5678-9012-3456'),
    new PagamentoBoleto(89.90, '12345678901234567890123456789012345678901234')
];

foreach ($pagamentos as $pagamento) {
    $pagamento->processar();
    echo "Taxa: R$ " . $pagamento->getTaxa() . "\n";
    echo "Status: " . $pagamento->getStatus() . "\n\n";
}
?>

Neste exemplo, a classe abstrata Pagamento fornece propriedades comuns (valor, status) e métodos concretos (getValor, getStatus). As subclasses implementam os métodos abstratos processar() e getTaxa() de acordo com suas regras de negócio. Isso permite tratar todos os pagamentos de forma uniforme, enquanto cada tipo mantém seu comportamento específico.

Boas práticas

  • Nomeie classes abstratas com nomes que indiquem generalidade, como Animal, Forma, Pagamento.
  • Evite criar classes abstratas muito grandes; prefira separar responsabilidades.
  • Use métodos abstratos apenas quando for absolutamente necessário que a subclasse implemente; caso contrário, forneça uma implementação padrão concreta.
  • Documente os métodos abstratos com PHPDoc para deixar claro o que se espera da implementação.
  • Considere usar interfaces se você não precisar compartilhar código; interfaces são mais flexíveis em cenários de múltiplas heranças.

Referências

Exercícios

  1. Crie uma classe abstrata Funcionario com propriedades nome e salarioBase, um método concreto getNome() e um método abstrato calcularSalario(). Depois, crie as classes FuncionarioHorista (que recebe por horas trabalhadas) e FuncionarioAssalariado (que recebe salário fixo) que implementam calcularSalario().

    ✓ Resposta:
    <?php
    abstract class Funcionario {
        protected $nome;
        protected $salarioBase;
    
        public function __construct($nome, $salarioBase) {
            $this->nome = $nome;
            $this->salarioBase = $salarioBase;
        }
    
        public function getNome() {
            return $this->nome;
        }
    
        abstract public function calcularSalario();
    }
    
    class FuncionarioHorista extends Funcionario {
        private $horasTrabalhadas;
        private $valorHora;
    
        public function __construct($nome, $salarioBase, $horasTrabalhadas, $valorHora) {
            parent::__construct($nome, $salarioBase);
            $this->horasTrabalhadas = $horasTrabalhadas;
            $this->valorHora = $valorHora;
        }
    
        public function calcularSalario() {
            return $this->horasTrabalhadas * $this->valorHora;
        }
    }
    
    class FuncionarioAssalariado extends Funcionario {
        public function calcularSalario() {
            return $this->salarioBase;
        }
    }
    ?>
  2. Explique por que uma classe abstrata não pode ser instanciada diretamente.

    ✓ Resposta: Uma classe abstrata não pode ser instanciada porque ela pode conter métodos abstratos sem implementação. Se fosse permitido instanciá-la, esses métodos não teriam corpo, tornando o objeto incompleto. A classe abstrata serve como modelo para subclasses que devem implementar os métodos abstratos.
  3. Qual a diferença entre uma classe abstrata e uma interface em PHP? Dê um exemplo de quando usar cada uma.

    ✓ Resposta: A principal diferença é que classes abstratas podem conter propriedades e métodos concretos, enquanto interfaces só podem declarar métodos (sem implementação) e constantes. Use classe abstrata quando você deseja compartilhar código entre subclasses (ex.: uma classe base Veiculo com um método getModelo() concreto). Use interface quando você quer apenas definir um contrato sem fornecer implementação (ex.: uma interface Imprimivel com método imprimir()).
  4. Considere a classe abstrata abaixo. Ela está correta? Se não, corrija.

    <?php
    abstract class Exemplo {
        abstract private function metodo();
    }
    ?>

    ✓ Resposta: Não está correta. Métodos abstratos não podem ser privados (private), pois precisam ser acessíveis pelas subclasses. A correção é alterar a visibilidade para protected ou public. Exemplo corrigido: abstract protected function metodo();.
  5. Crie uma classe abstrata Midia com propriedades titulo e ano, um método concreto getTitulo() e um método abstrato getTipo(). Crie duas subclasses: Filme e Musica. Na classe Filme, adicione uma propriedade diretor e faça getTipo() retornar "Filme". Na classe Musica, adicione uma propriedade artista e faça getTipo() retornar "Música".

    ✓ Resposta:
    <?php
    abstract class Midia {
        protected $titulo;
        protected $ano;
    
        public function __construct($titulo, $ano) {
            $this->titulo = $titulo;
            $this->ano = $ano;
        }
    
        public function getTitulo() {
            return $this->titulo;
        }
    
        abstract public function getTipo();
    }
    
    class Filme extends Midia {
        private $diretor;
    
        public function __construct($titulo, $ano, $diretor) {
            parent::__construct($titulo, $ano);
            $this->diretor = $diretor;
        }
    
        public function getTipo() {
            return "Filme";
        }
    }
    
    class Musica extends Midia {
        private $artista;
    
        public function __construct($titulo, $ano, $artista) {
            parent::__construct($titulo, $ano);
            $this->artista = $artista;
        }
    
        public function getTipo() {
            return "Música";
        }
    }
    ?>