I've found this pattern useful, and am trying to classify or name it.
Basically, that:
- A task should be performed by different strategies, depending on the
context
. - Each concrete
strategy
implements a commoninterface
. - BUT... caller doesn't choose the strategy.
- Instead there's a priority-ordered list of the available strategies,
and eachstrategy
chooses based oncontext
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->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:
- Decoupling - each strategy gets to decide which contexts it supports.
- Separation of concerns - each quirky bit of business logic is clearly owned by that class
- Polymorphism - a strategy that is a subset of another can inherit and override as needed
No specific downsides that I've encountered, yet.