SOLID: Open Closed + Strategy (Parte 4)

·2 min de leitura

Open/Closed sem misticismo

O Open/Closed Principle (OCP) é frequentemente mal interpretado como "nunca editar arquivo antigo". Não é isso.

A ideia útil é:

Você consegue adicionar comportamento novo com baixo risco para o comportamento existente, normalmente estendendo (via polimorfismo/composição) em vez de editar um núcleo instável cheio de condicionais.

Onde OCP dói de verdade

Quando cada nova regra vira mais um elseif no mesmo método. Isso cresce, assusta e quebra testes por qualquer motivo.

Strategy como alavanca de OCP

Na Parte 2 da série de Design Patterns, vimos Strategy: família de algoritmos intercambiáveis.

OCP "adora" Strategy porque o contexto permanece estável, novas variações entram como novas classes e você reduz modificações no miolo do fluxo.

Exemplo na prática: imposto com OCP + Strategy

<?php

declare(strict_types=1);

interface TaxRule
{
    public function appliesTo(string $category): bool;
    public function rate(): float;
}

final class TaxCalculator
{
    /** @param list<TaxRule> $rules */
    public function __construct(private array $rules) {}

    public function taxFor(string $category, float $price): float
    {
        foreach ($this->rules as $rule) {
            if ($rule->appliesTo($category)) {
                return $price * $rule->rate();
            }
        }

        return 0.0;
    }
}

final readonly class ElectronicsTax implements TaxRule
{
    public function appliesTo(string $category): bool
    {
        return $category === 'electronics';
    }

    public function rate(): float
    {
        return 0.20;
    }
}

final readonly class BookTax implements TaxRule
{
    public function appliesTo(string $category): bool
    {
        return $category === 'books';
    }

    public function rate(): float
    {
        return 0.05;
    }
}

Uso:

$calculator = new TaxCalculator([
    new ElectronicsTax(),
    new BookTax(),
]);

$calculator->taxFor('electronics', 100.0); // 20.0
$calculator->taxFor('books', 50.0);        // 2.5
$calculator->taxFor('food', 30.0);         // 0.0 (nenhuma regra aplica)

Note que 'food' devolveu 0.0 porque nenhuma regra cobre essa categoria. Para cobrir, basta criar uma GroceryTax implements TaxRule — sem tocar em TaxCalculator. Esse é o ponto do OCP: o núcleo não muda quando o comportamento cresce.

OCP não substitui bom senso

Às vezes um match pequeno é mais claro do que 15 classes. OCP é um guia de custo de mudança, não um troféu.

Conclusão

OCP pede extensibilidade com segurança. Strategy (e outros polimorfismos) são caminhos comuns, especialmente quando regras crescem com o tempo.