Desafio PHP

Registro de classes com fábrica e contador

Enunciado

Crie uma classe abstrata Animal com uma propriedade estática $total e um método estático criar que recebe um array com tipo e outros dados. O método deve usar static:: para instanciar a classe correta (Cachorro ou Gato) com base no tipo. Cada classe filha deve ter seu próprio construtor. O contador $total deve ser incrementado a cada criação. Exiba o total e os dados dos animais criados.

Requisitos

  • Animal deve ser abstrata e ter public static int $total = 0.
  • O método criar deve usar static:: e uma estrutura de decisão para escolher a subclasse.
  • As classes Cachorro e Gato devem estender Animal e ter propriedades específicas.
  • O contador deve ser incrementado no construtor de Animal ou de cada subclasse.

Código inicial

<?php

abstract class Animal
{
    public static int $total = 0;

    public function __construct(public readonly string $nome) {
        // TODO: incremente o contador
    }

    // TODO: implemente o método estático criar
}

class Cachorro extends Animal
{
    // TODO: propriedade $raca e construtor
}

class Gato extends Animal
{
    // TODO: propriedade $cor e construtor
}

// TODO: use Animal::criar para criar animais e exiba os dados

Saída esperada

Cachorro: Rex (Labrador)
Gato: Mimi (Branco)
Total de animais: 2
Ver dica

No método criar, verifique $dados['tipo'] e retorne new Cachorro(...) ou new Gato(...). Use static:: para permitir que subclasses estendam a fábrica. O contador pode ser incrementado no construtor de Animal.

Mostrar solução
<?php

abstract class Animal
{
    public static int $total = 0;

    public function __construct(public readonly string $nome)
    {
        self::$total++; // incrementa o contador global
    }

    // Fábrica estática que escolhe a subclasse com base no tipo
    public static function criar(array $dados): static
    {
        return match ($dados['tipo']) {
            'cachorro' => new Cachorro($dados['nome'], $dados['raca']),
            'gato' => new Gato($dados['nome'], $dados['cor']),
            default => throw new InvalidArgumentException('Tipo desconhecido'),
        };
    }

    abstract public function exibir(): string;
}

class Cachorro extends Animal
{
    public function __construct(string $nome, public readonly string $raca)
    {
        parent::__construct($nome);
    }

    public function exibir(): string
    {
        return "Cachorro: {$this->nome} ({$this->raca})";
    }
}

class Gato extends Animal
{
    public function __construct(string $nome, public readonly string $cor)
    {
        parent::__construct($nome);
    }

    public function exibir(): string
    {
        return "Gato: {$this->nome} ({$this->cor})";
    }
}

// Criando animais via fábrica
$cachorro = Animal::criar(['tipo' => 'cachorro', 'nome' => 'Rex', 'raca' => 'Labrador']);
$gato = Animal::criar(['tipo' => 'gato', 'nome' => 'Mimi', 'cor' => 'Branco']);

echo $cachorro->exibir() . PHP_EOL;
echo $gato->exibir() . PHP_EOL;
echo 'Total de animais: ' . Animal::$total . PHP_EOL;

Passo a passo

  1. Animal é abstrata e define public static int $total = 0 para contar todas as instâncias.
  2. O construtor de Animal recebe $nome e incrementa self::$total. Como as subclasses chamam parent::__construct, o contador é atualizado.
  3. O método estático criar usa match para decidir qual subclasse instanciar com base em $dados['tipo']. Retorna static para permitir que subclasses reutilizem a fábrica.
  4. Cachorro e Gato estendem Animal, adicionam propriedades específicas e implementam exibir.
  5. Chamamos Animal::criar duas vezes e exibimos os dados e o total.

Por que funciona

O late static binding em static:: permite que o método criar seja herdado e ainda assim retorne o tipo correto. O contador estático em Animal é compartilhado por todas as subclasses, pois é definido na classe base. Assim, cada criação incrementa o mesmo contador.

Erros comuns

  • Usar self no retorno de criar: return new self(...) não funcionaria porque Animal é abstrata. Além disso, static é necessário para que subclasses possam sobrescrever a fábrica.
  • Esquecer de chamar parent::__construct: o contador não seria incrementado e $nome não seria definido.
  • Não tratar tipo desconhecido: o match sem default lançaria UnhandledMatchError. Usamos throw para dar uma mensagem clara.

Outra forma de resolver

Você pode usar um método estático em cada subclasse em vez de uma fábrica central. Por exemplo, Cachorro::criar($dados) e Gato::criar($dados). A fábrica central é útil quando o tipo é dinâmico e você quer um ponto único de criação.

Saída esperada

Cachorro: Rex (Labrador)
Gato: Mimi (Branco)
Total de animais: 2