coder_1432
coder_1432

Reputation: 283

Multiple Strategies in Strategy Pattern Context

I have a question about the strategy pattern. Usually the Strategy Pattern looks like this:

class TaxCalculatorContext
{
    private $strategy;

    public function setStrategy(TaxCalculatorStrategyInterface $strategy)
    {
        $this->strategy = $strategy;
    }

    public function execute($amount)
    {
        return $this->strategy->calculate($amount);
    }
}

class TaxCalculatorOntario implements TaxCalulatorStrategyInterface
{
    public function calculate($amount)
    {
        ...calculate ontario taxes here
    }
}

class TaxCalculatorQuebec implements TaxCalculatorStrategyInterface
{
    public function calculate($amount)
    {
        ...calculate quebec taxes here
    }
}

interface TaxCalculatorStrategyInterface
{
    public function calculate($amount);
}

I would like to know if It’s an acceptable practice to use multiple strategies in a context. Please look at the code below.

class TaxCalculatorContext
{
    private $strategies;

    public function addStrategy(TaxCalculatorStrategyInterface $strategy)
    {
        $this->strategies[] = $strategy;
    }

    public function execute($amount)
    {
        foreach ($this->strategies as $strategy)
        {
            if($strategy->canCalculate)
            {
                return $strategy->calculate($amount);
            }
        }
    }
}

class TaxCalculatorOntario implements TaxCalulatorStrategyInterface
{
    public function canCalculate($amount)
    {
        ... returns true or false
    }

    public function calculate($amount)
    {
        ...calculate ontario taxes here
    }
}

class TaxCalculatorQuebec implements TaxCalculatorStrategyInterface
{
    public function canCalculate($amount)
    {
        ... returns true or false
    }

    public function calculate($amount)
    {
        ...calculate quebec taxes here
    }
}

interface TaxCalculatorStrategyInterface
{
    public function canCalculate($amount);
    public function calculate($amount);
}

As you can see, instead of just passing one strategy, I created an array of strategies in the TaxCalculatorContext. Then when the execute method is called, strategies are looped though and the first one that returns true in canCalulate method will be executed. So is this a standard practice or I should avoid it?

Upvotes: 1

Views: 4054

Answers (1)

Julian Suggate
Julian Suggate

Reputation: 687

If it suits your problem, then I don't see any reason why you wouldn't. If you are retrofitting this into an existing strategy structure, then you could investigate using the Composite pattern. The way you would use it is to create a new subclass of strategy that accepts multiple other strategies to its constructor or factory. Then, internally to the strategy itself it will execute the sub-strategies as necessary. That way, you don't need to go changing the Strategy interface, and the client code that invokes the strategy won't need to be aware that there even are multiple strategies.

But if you are not retrofitting, and the idea of having multiple strategies is integral to the problem domain, then you could arguably avoid the added complexity of implementing Composite, and just proceed as you have indicated. Throwing more and more patterns at a thing is not always necessary, or efficient in terms of velocity. Just know that Composite is there as an option for future refactoring, if you ever want to decouple the idea of multiple strategies from client code.

Two remarks on the example you gave (which you may already know but just in case): firstly, as currently written the multiple strategies exemplar will execute each strategy whose predicate returns true, not just the first one (you need a break statement after calculating the first one if you wish that behaviour). Secondly, the more state you have scattered around your application, the more potential bugs you are going to have. So might want to think about returning the calculated value back out of Strategy::calculate() instead of storing it on the strategy as the interface currently implies. This will make your strategies into stateless functors, which is desirable because then you don't have to think about managing their state.

Upvotes: 5

Related Questions