A wall socket doesn't get rewired every time you buy a new appliance — it stays closed to change, while new devices plug into its open, standard interface. Well-designed modules work the same way: closed for modification, open for extension.
At Infracommerce, integrating a new payment or shipping method for a Magento store meant implementing the module's own gateway interface and registering it — the checkout and cart logic never needed to change to support a new processor or a client-specific carrier. That's Open/Closed showing up directly in a commerce platform's plugin architecture: the core stayed closed, each integration was purely additive.
Trade-off: the interface exists before there's a second implementation to justify it — for a while, PaymentGateway has exactly one class behind it, and anyone reading the code has to trust that a second processor is actually coming, not just imagine one.
PaymentProcessor
Before
class PaymentProcessor
{
public function charge(string $method, Money $amount): Receipt
{
if ($method === 'stripe') {
return $this->chargeStripe($amount);
} elseif ($method === 'paypal') {
return $this->chargePaypal($amount);
}
throw new InvalidArgumentException('Unknown payment method');
}
}After
interface PaymentGateway
{
public function charge(Money $amount): Receipt;
}
class PaymentProcessor
{
public function __construct(private PaymentGateway $gateway) {}
public function charge(Money $amount): Receipt
{
return $this->gateway->charge($amount);
}
}
// Adding Klarna later: implement PaymentGateway, no changes here