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->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.

1 Answer 1

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.

4
  • Also note that the Wikipedia page examples always pass on the message, even when they handled the message themselves. The original description in the GoF book explicitly states: "... until an object handles it.". Even the description on Wikipedia states "... to either handle a request or forward it ...", but the examples do something different.
    – Quido
    Oct 5, 2022 at 14:22
  • Aha, thank you! It does seem a little bit odd with the CoR pattern that the processing objects are aware of one another (at least the next object)
    – MrTrick
    Oct 5, 2022 at 16:09
  • @MrTrick - "It does seem a little bit odd [...] that the processing objects are aware of one another" - Chain of Responsibility is often used in situations where you already have objects arranged in some type of structure that has chains in it. E.g., user interface control trees (controls containing child controls, containing child controls, etc.). The click event (for example) is passed down a chain of controls that contain the click location, until one of the controls indicates that it has handled it (it might actually go down from the parent, then bubble back up again). Oct 5, 2022 at 17:32
  • @MrTrick, typically, constructing the "pipeline" of handlers is either done based on some structure you already have as Filip M. mentions (although this smells more like a Visitor to me), or at startup of your application. You typically create a chain and reuse that for handling many messages. The chain determines the logic of your application, so it often does not change run-time.
    – Quido
    Oct 6, 2022 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.