1

I've found this pattern useful, and am trying to classify or name it.
Basically, that:

  1. A task should be performed by different strategies, depending on the context.
  2. Each concrete strategy implements a common interface.
  3. BUT... caller doesn't choose the strategy.
  4. Instead there's a priority-ordered list of the available strategies,
    and each strategy chooses based on context whether it will accept (and claim) the task.

Does it sound like another named pattern? Or what would you call it?


For example:

interface InvoicePublisherInterface {
    function accepts($context): bool;
    function publish($context): Result;
}

/** @var array<class-string<InvoicePublisherInterface>> $publisherClasses */ $publisherClasses = [ InvoicePublisherFranceCarrefour::class, // Company-specific publisher - eg special business logic InvoicePublisherFrance::class, // Country-specific publishers InvoicePublisherSpain::class, // ... InvoicePublisherDefault::class, // Fallback if no others match ];

// Find which publisher to use function resolveInvoicePublisher(Context $context): InvoicePublisherInterface { foreach($publisherClasses as $publisherClass) { $publisher = new $publisherClass(); // (a real implementation would reuse instances)

    if ($publisher-&gt;accepts($context)) {
        return $publisher;
    }
}
throw new LogicException('Could not resolve publisher');

}

...

// Here is some code that publishes invoices, but doesn't need to know how its done.

$publisher = resolveInvoicePublisher($context);

$result = $publisher->publish($context);

The order of strategies matters, as one strategy might override another - for instance the FranceCarrefour publisher overrides the France publisher for specific contexts:

class InvoicePublisherFrance implements InvoicePublisherInterface {
    public function accepts($context): bool { 
        return $context->country === 'france';
    }
    public function publish($context): Result {
        // do some france invoice publishing...
    }   
}

class InvoicePublisherFranceCarrefour extends InvoicePublisherFrance { public function accepts($context): bool { return $context->country === 'france' && $context->company === 'carrefour'; } ... //override some parts of the parent to apply company-specific business logic... }

Some advantages I've found in addition to the basic strategy pattern are:

  1. Decoupling - each strategy gets to decide which contexts it supports.
  2. Separation of concerns - each quirky bit of business logic is clearly owned by that class
  3. Polymorphism - a strategy that is a subset of another can inherit and override as needed

No specific downsides that I've encountered, yet.

MrTrick
  • 113

1 Answers1

1

The intent of this pattern, to me, sounds like the Chain of Responsibility (CoR), defined by the Gang of Four.

The official definition of its intent:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

In that pattern, a chain of handlers is set up, like your $publisherClasses. Every handler decides whether to handle the object itself or pass it on to the next handler in de line. The pattern doesn't describe how a handler should decide to handle or pass on.

In your question you mention: "The order of strategies matters, as one strategy might override another." In the CoR the order is vital. Not that one strategy might override, but the first handler that accepts will override all others.

Check Wikipedia for more information: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern

Note that the structure of the Chain of Responsibility is very similar to the Decorator pattern (also GoF), but the intent is completely different.

Quido
  • 326