Skip to content

11.1 Padrões criacionais (Creational Patterns) ​

Resumo ​

Creational Patterns — padrões para criar objetos. Abstraem o processo de instanciação.

Principais: Singleton (uma instância), Factory Method (cria pelo tipo), Abstract Factory (famílias de objetos), Builder (criação complexa), Prototype (clonagem).

Exemplos no Laravel: Service Container singleton, Model Factories, Query Builder.


Conteúdo ​


O que é ​

Creational Patterns: Padrões para criar objetos. Abstraem o processo de instanciação.

Para quê:

  • Criar objetos com flexibilidade
  • Esconder a complexidade da criação
  • Reaproveitar código

Padrões principais:

  1. Singleton
  2. Factory Method
  3. Abstract Factory
  4. Builder
  5. Prototype

1. Singleton ​

O que é: Garante que a classe tenha uma instância só e oferece um ponto global de acesso.

Quando usar:

  • Database connection
  • Logger
  • Configuration
  • Cache manager

Implementação:

php
class Database
{
    private static ?Database $instance = null;
    private PDO $connection;

    // Constructor private (não dá para criar de fora)
    private function __construct()
    {
        $this->connection = new PDO(
            'mysql:host=localhost;dbname=mydb',
            'user',
            'password'
        );
    }

    // Clone private (não dá para clonar)
    private function __clone() {}

    // unserialize bloqueado (não dá para desserializar)
    public function __wakeup()
    {
        throw new Exception("Não é possível desserializar um singleton");
    }

    public static function getInstance(): Database
    {
        if (self::$instance === null) {
            self::$instance = new Database();
        }

        return self::$instance;
    }

    public function getConnection(): PDO
    {
        return $this->connection;
    }
}

// Uso
$db1 = Database::getInstance();
$db2 = Database::getInstance();

var_dump($db1 === $db2);  // true (o mesmo objeto)

Singleton no Laravel:

php
// Service Provider
$this->app->singleton(PaymentGateway::class, function ($app) {
    return new StripeGateway(config('stripe.key'));
});

// Em todo lugar a mesma instância
$gateway1 = app(PaymentGateway::class);
$gateway2 = app(PaymentGateway::class);

var_dump($gateway1 === $gateway2);  // true

Contras do Singleton:

  • ❌ Global state (testar fica mais difícil)
  • ❌ Tight coupling
  • ❌ Multithreading issues

Alternativa: Dependency Injection

php
// No lugar do Singleton
class OrderService
{
    public function __construct(
        private PaymentGateway $gateway  // DI no lugar do Singleton
    ) {}
}

2. Factory Method ​

O que é: Define a interface para criar objetos, mas deixa a subclasse decidir qual classe instanciar.

Quando usar:

  • Você não sabe de antemão o tipo do objeto
  • A criação fica a cargo das subclasses

Problema sem Factory:

php
// Ruim: if/switch no código do cliente
$type = 'credit_card';

if ($type === 'credit_card') {
    $gateway = new CreditCardGateway();
} elseif ($type === 'paypal') {
    $gateway = new PayPalGateway();
} elseif ($type === 'crypto') {
    $gateway = new CryptoGateway();
}

$gateway->charge($amount);

Solução: Factory Method

php
abstract class PaymentGatewayFactory
{
    abstract public function createGateway(): PaymentGateway;

    public function processPayment(int $amount): Payment
    {
        $gateway = $this->createGateway();
        return $gateway->charge($amount);
    }
}

class CreditCardGatewayFactory extends PaymentGatewayFactory
{
    public function createGateway(): PaymentGateway
    {
        return new CreditCardGateway();
    }
}

class PayPalGatewayFactory extends PaymentGatewayFactory
{
    public function createGateway(): PaymentGateway
    {
        return new PayPalGateway();
    }
}

// Uso
$factory = new CreditCardGatewayFactory();
$payment = $factory->processPayment(10000);  // R$ 100,00 em centavos

Simple Factory (não é o padrão Gang of Four, mas é útil):

php
class PaymentGatewayFactory
{
    public static function create(string $type): PaymentGateway
    {
        return match ($type) {
            'credit_card' => new CreditCardGateway(),
            'paypal' => new PayPalGateway(),
            'crypto' => new CryptoGateway(),
            default => throw new InvalidArgumentException("Tipo desconhecido: {$type}"),
        };
    }
}

// Uso
$gateway = PaymentGatewayFactory::create('credit_card');
$gateway->charge($amount);

Factory do Laravel para Models:

php
// database/factories/UserFactory.php
class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'password' => Hash::make('password'),
        ];
    }

    public function admin()
    {
        return $this->state(fn (array $attributes) => [
            'is_admin' => true,
        ]);
    }
}

// Uso
$user = User::factory()->create();
$admin = User::factory()->admin()->create();

3. Abstract Factory ​

O que é: Oferece uma interface para criar famílias de objetos relacionados, sem apontar as classes concretas.

Quando usar:

  • Você precisa criar famílias de objetos relacionados
  • O sistema não pode depender de como os objetos são criados

Exemplo: UI Components

php
// Abstract Factory
interface UIFactory
{
    public function createButton(): Button;
    public function createCheckbox(): Checkbox;
}

// Concrete Factories
class WindowsUIFactory implements UIFactory
{
    public function createButton(): Button
    {
        return new WindowsButton();
    }

    public function createCheckbox(): Checkbox
    {
        return new WindowsCheckbox();
    }
}

class MacUIFactory implements UIFactory
{
    public function createButton(): Button
    {
        return new MacButton();
    }

    public function createCheckbox(): Checkbox
    {
        return new MacCheckbox();
    }
}

// Products
interface Button
{
    public function render(): string;
}

class WindowsButton implements Button
{
    public function render(): string
    {
        return '<button class="windows">Clique</button>';
    }
}

class MacButton implements Button
{
    public function render(): string
    {
        return '<button class="mac">Clique</button>';
    }
}

// Client
class Application
{
    private Button $button;
    private Checkbox $checkbox;

    public function __construct(UIFactory $factory)
    {
        $this->button = $factory->createButton();
        $this->checkbox = $factory->createCheckbox();
    }

    public function render(): string
    {
        return $this->button->render() . $this->checkbox->render();
    }
}

// Uso
$os = 'windows';
$factory = $os === 'windows' ? new WindowsUIFactory() : new MacUIFactory();

$app = new Application($factory);
echo $app->render();

Exemplo no Laravel: Notification Channels

php
interface NotificationFactory
{
    public function createEmailChannel(): EmailChannel;
    public function createSmsChannel(): SmsChannel;
}

class ProductionNotificationFactory implements NotificationFactory
{
    public function createEmailChannel(): EmailChannel
    {
        return new SmtpEmailChannel(config('mail.smtp'));
    }

    public function createSmsChannel(): SmsChannel
    {
        return new TwilioSmsChannel(config('services.twilio'));
    }
}

class TestingNotificationFactory implements NotificationFactory
{
    public function createEmailChannel(): EmailChannel
    {
        return new LogEmailChannel();  // Loga em vez de enviar
    }

    public function createSmsChannel(): SmsChannel
    {
        return new LogSmsChannel();
    }
}

4. Builder ​

O que é: Separa a construção de um objeto complexo da representação dele. O mesmo processo monta representações diferentes.

Quando usar:

  • O objeto tem muitos parâmetros opcionais
  • A criação tem vários passos

Problema sem Builder:

php
// Telescoping Constructor Anti-Pattern
class Pizza
{
    public function __construct(
        private string $size,
        private bool $cheese = false,
        private bool $pepperoni = false,
        private bool $bacon = false,
        private bool $mushrooms = false,
        private bool $olives = false
    ) {}
}

// Uso: difícil de ler
$pizza = new Pizza('large', true, false, true, false, true);

Solução: Builder

php
class Pizza
{
    private string $size;
    private bool $cheese = false;
    private bool $pepperoni = false;
    private bool $bacon = false;

    private function __construct() {}

    public static function builder(): PizzaBuilder
    {
        return new PizzaBuilder();
    }

    // Getters...
}

class PizzaBuilder
{
    private Pizza $pizza;

    public function __construct()
    {
        $this->pizza = new Pizza();
    }

    public function size(string $size): self
    {
        $this->pizza->size = $size;
        return $this;
    }

    public function withCheese(): self
    {
        $this->pizza->cheese = true;
        return $this;
    }

    public function withPepperoni(): self
    {
        $this->pizza->pepperoni = true;
        return $this;
    }

    public function withBacon(): self
    {
        $this->pizza->bacon = true;
        return $this;
    }

    public function build(): Pizza
    {
        return $this->pizza;
    }
}

// Uso: fluent, fácil de ler
$pizza = Pizza::builder()
    ->size('large')
    ->withCheese()
    ->withBacon()
    ->build();

Laravel Query Builder:

php
// Laravel Query Builder = Builder Pattern!
$users = DB::table('users')
    ->where('active', true)
    ->where('age', '>', 18)
    ->orderBy('name')
    ->limit(10)
    ->get();

// Eloquent Builder
$posts = Post::query()
    ->with('author')
    ->where('published', true)
    ->latest()
    ->paginate(20);

HTTP Request Builder:

php
$response = Http::withHeaders([
        'X-Api-Key' => 'secret',
    ])
    ->timeout(30)
    ->retry(3, 100)
    ->post('https://api.example.com/users', [
        'name' => 'João',
    ]);

5. Prototype ​

O que é: Cria objetos novos copiando (clonando) os que já existem.

Quando usar:

  • Criar o objeto é caro (DB query, API call)
  • Você precisa de vários objetos parecidos

Implementação:

php
class Product
{
    public function __construct(
        public string $name,
        public int $price,  // centavos
        public array $attributes = []
    ) {}

    public function __clone()
    {
        // Deep clone para arrays/objetos
        $this->attributes = array_map(
            fn($attr) => is_object($attr) ? clone $attr : $attr,
            $this->attributes
        );
    }
}

// Criar o prototype
$prototype = new Product('Notebook', 499900, [  // R$ 4.999,00
    'brand' => 'Dell',
    'warranty' => '2 anos',
]);

// Clonar e modificar
$product1 = clone $prototype;
$product1->name = 'Notebook Gamer';
$product1->price = 749900;  // R$ 7.499,00

$product2 = clone $prototype;
$product2->name = 'Notebook Corporativo';
$product2->price = 599900;  // R$ 5.999,00

Laravel Eloquent:

php
// Replicate = Prototype Pattern
$user = User::find(1);

$newUser = $user->replicate();
$newUser->email = 'joao@email.com';
$newUser->save();

// Replicate com relationships
$post = Post::with('tags')->find(1);
$newPost = $post->replicate();
$newPost->push();  // Salva com os relationships

Comparação ​

PatternCaso de usoExemplo no Laravel
SingletonUma instânciaService Container singleton
Factory MethodCriar pelo tipoModel factories
Abstract FactoryFamílias de objetosNotification channels
BuilderCriação complexaQuery Builder, HTTP Builder
PrototypeClonagemModel replicate()

Na entrevista ​

"Creational Patterns são para criar objetos. Singleton: uma instância só, no Laravel é singleton() no Container. Factory Method: cria pelo tipo, Simple Factory com match. Abstract Factory: famílias de objetos relacionados. Builder: fluent interface para objeto complexo, Query Builder do Laravel é o exemplo. Prototype: clona o objeto, no Eloquent é replicate(). Factory e Builder são os mais comuns no Laravel. Singleton quase não precisa — DI é melhor. Builder quando a API tem parâmetro opcional e você quer leitura fácil."


Exercícios práticos ​

Exercício 1: Implemente uma Simple Factory ​

Crie um PaymentFactory com o método create() que devolve um payment gateway diferente conforme o tipo: stripe, paypal, crypto.

Solução
php
interface PaymentGateway
{
    public function charge(int $amount): Payment;
}

class StripeGateway implements PaymentGateway
{
    public function charge(int $amount): Payment
    {
        // Lógica do Stripe
        return new Payment('stripe', $amount);
    }
}

class PayPalGateway implements PaymentGateway
{
    public function charge(int $amount): Payment
    {
        // Lógica do PayPal
        return new Payment('paypal', $amount);
    }
}

class PaymentFactory
{
    public static function create(string $type): PaymentGateway
    {
        return match ($type) {
            'stripe' => new StripeGateway(),
            'paypal' => new PayPalGateway(),
            'crypto' => new CryptoGateway(),
            default => throw new InvalidArgumentException("Tipo desconhecido: {$type}"),
        };
    }
}

// Uso
$gateway = PaymentFactory::create('stripe');
$payment = $gateway->charge(10000);  // R$ 100,00 em centavos

Exercício 2: Implemente o Builder Pattern ​

Crie um QueryBuilder para montar SQL com os métodos select(), where(), orderBy(), limit().

Solução
php
class QueryBuilder
{
    private string $table;
    private array $selects = ['*'];
    private array $wheres = [];
    private ?string $orderBy = null;
    private ?int $limit = null;

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

    public function select(array $columns): self
    {
        $this->selects = $columns;
        return $this;
    }

    public function where(string $column, string $operator, mixed $value): self
    {
        $this->wheres[] = [$column, $operator, $value];
        return $this;
    }

    public function orderBy(string $column, string $direction = 'ASC'): self
    {
        $this->orderBy = "{$column} {$direction}";
        return $this;
    }

    public function limit(int $limit): self
    {
        $this->limit = $limit;
        return $this;
    }

    public function toSql(): string
    {
        $sql = "SELECT " . implode(', ', $this->selects);
        $sql .= " FROM {$this->table}";

        if ($this->wheres) {
            $conditions = array_map(
                fn($w) => "{$w[0]} {$w[1]} '{$w[2]}'",
                $this->wheres
            );
            $sql .= " WHERE " . implode(' AND ', $conditions);
        }

        if ($this->orderBy) {
            $sql .= " ORDER BY {$this->orderBy}";
        }

        if ($this->limit) {
            $sql .= " LIMIT {$this->limit}";
        }

        return $sql;
    }
}

// Uso
$sql = (new QueryBuilder('users'))
    ->select(['name', 'email'])
    ->where('active', '=', 1)
    ->orderBy('created_at', 'DESC')
    ->limit(10)
    ->toSql();

Exercício 3: Qual é o problema do Singleton? ​

Por que o Singleton é considerado um antipattern? Qual é a alternativa?

Solução

Problemas do Singleton:

  1. Global State — estado global mutável deixa o teste mais difícil
  2. Tight Coupling — o código fica amarrado na classe concreta
  3. Hidden Dependencies — a dependência não aparece no construtor
  4. Difícil de testar — não dá para trocar por um mock
  5. Multithreading issues — problema em ambiente com várias threads

Alternativa: Dependency Injection

php
// Ruim: Singleton
class OrderService
{
    public function process(Order $order)
    {
        $gateway = PaymentGateway::getInstance();  // Dependência escondida
        $gateway->charge($order->total);
    }
}

// Bom: DI
class OrderService
{
    public function __construct(
        private PaymentGateway $gateway  // Dependência explícita
    ) {}

    public function process(Order $order)
    {
        $this->gateway->charge($order->total);
    }
}

// No Service Container
$this->app->singleton(PaymentGateway::class, StripeGateway::class);

Quando o Singleton é aceitável:

  • Log (operação simples, só escreve)
  • Configuration (dado read-only)
  • Connection pools (recurso compartilhado sob controle)

Parte do PHP/Laravel Interview Handbook | Feito com ❤️ pela equipe CodeMate

Publicado sob a licença MIT