What is Strategy Pattern?

Asked

Viewed 408 times

7

I was reading about Strategy Pattern, however, I could not understand it very well and I was confused about its functioning.

Doubts

  1. What is the Strategy Pattern?
  2. In which scenarios should I use it?

Preferably I would like examples in C# or PHP if possible. But feel free to give examples in any language.

2 answers

8


What is the Strategy Pattern?

It occurs when an object may have different ways of behaving with respect to a specific aspect of it.

It is very much mistaken for others or is used along with other patterns. It is very similar to the Adapter, which is already very similar to the Bridge and ends up serving as Stabbing. He also remembers the Decorator and even the Visitor. Even State is similar and often confused. Others have similar characteristics. I’m not saying they do the same thing, but they all serve to add something to an object without having to touch itself. A lot of patterns change for small details, which can make it difficult to choose the most suitable.

The traditional way to deal with this is to have a parameter (fixed as part of the structure of an object or circumstantial only received by the behavior (method) and make a decision of which code to execute based on it. All codes are closely linked to the object and the selection of which to execute is usually made by a switch.

This has some drawbacks:

  • the class or method has various responsibilities
  • need to have sources to add or modify a new strategy
  • Adding a new strategy can create problems with inheritance and other ways of composing methods for this type
  • the addition of new strategy will likely require several independent maintenance points.

It is common to have a context selection class, which makes the management of the strategy choice. This is useful because it keeps the strategy uncoupled from the object that needs to consume it.

Some people use a Factory to choose the strategy that will be applied. In the examples there end up implementing a form of SP. The factory can replace what is called context.

Most of the articles that teach the patterns are somewhat academic using abstract terms for each thing and it’s not always easy to link these terms as real use.

Strategy pattern

In which scenarios should I use it?

I think this is set above, I’ll go to some real examples.

One of them can be seen in a question from a subject there :P

Another example exists on the site about the Gof.

In my humble opinion the Java example of the Wikipedia page linked in the question is bad or even wrong. The fact of putting everything together in one place only loses the ability to create new strategies independently. If it is to do this I prefer the simplest solution to have or a single class that takes care of everything or independent functions without classes.

Logs

Think of an application you need to generate logs of activities and how to decide where and how log in between file, mail, Mysql, and who knows later may have syslog, Sqlite, etc. The default is used to decide what to use.

Taxes

A typical example could be the tax calculation of the marketing of a product. It is true that the product should only deal with things that directly concern it. But calculating his marketing tax is something to do with him. Each Federative unit has a different way of calculating the tax, either by the formula or by the different rate or basis of calculation, in addition to treating different exceptions.

The best solution is to allow the calculation to be determined externally. It creates a base abstract class or interface for this calculation and each state derives from it to implement the calculation of its form. The product class only needs to know that there is the calculation, which is it will be determined by the context of the use of the object.

If the product is being marketed in São Paulo, call the calculation method and possibly others of this class, if the operation takes place in another state will use another class with the same contract.

If one day you create a new state, just create a new class, if you have new forms that are not dependent on the state, you can add them without changing anything in any code, just add the new calculation strategy.

using static System.Console;

public class Program {
    public static void Main() {
        var calc = new CalculaImposto(new CalculaImpostoSP());
        WriteLine("Imposto SP: " + calc.Calcula(1500M)); //aqui obviamente pegaria o valor do produto na classe específica
        calc.Strategy = new CalculaImpostoRJ();
        WriteLine("Imposto RJ: " + calc.Calcula(1500M));
    }
}

public interface ICalculaImposto {
    decimal Calcula(decimal baseCalculo);
}

public class CalculaImpostoSP : ICalculaImposto {
    public decimal Calcula(decimal baseCalculo) => baseCalculo * 0.18M;
}

public class CalculaImpostoRJ : ICalculaImposto {
    public decimal Calcula(decimal baseCalculo) => baseCalculo * 0.12M;
}

public class CalculaImposto {
    public ICalculaImposto Strategy { get; set; } //constuma-se considerar isto obrigatório para cumprir o padrão
    public CalculaImposto(ICalculaImposto strategy) { Strategy = strategy; }
    public decimal Calcula(decimal baseCalculo) => Strategy.Calcula(baseCalculo);
}

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

Lambda

Although officially it is planned to use with classes and object orientation, it can be applied more or less equal without this feature. The use of delegates or Amble serve much in this regard, although some do not consider the use of this mechanism the same as making the Strategy Pattern.

Think about it, if you have a code that knows what to do, but you need to have a lot of different strategies that you might want, sending a form method is the same thing that the strategy standard does. Since the method sent to this algorithm has the desired contract, that is, it receives the arguments that will be sent to it and returns what is expected of it, it is an appropriate strategy.

So it has programmer who abuse it and creates a complex solution to solve a simple problem.

6

The strategy pattern is useful when you have several ways to accomplish a certain task. Its intention is to separate components with an equal purpose, however these components work differently.

An easy-to-understand example is trying to validate a string. You want to apply validation rules to a string, for example:

Has a maximum size of 100 characters.
You can’t start with a number.

Can only contain numbers and letters.

If you implement these rules without the strategy pattern the code looks something like this:

var value = "A string para eu validar";
var isValid = value.Length <= 100
    && !char.IsDigit(value[0]) 
    && value..All(c => char.IsLetterOrDigit(c))

if(!isValid){
  throw new InvalidOperationException("A string não é válida");
}

If you apply the strategy pattern you implement a class for each validation rule. Of course these classes have to have some flexibility, for example one day you might want to validate 80-character strings, so it’s a good idea to draw your class that validates the size to support any size. Here is the same example applying the strategy pattern

public IValidator{
    bool IsValida(string value);
}

public class StringMaximumSizeValidator : IValidator{

    private int _size;
    
    publiic StringMaximumSizeValidator (inte size){
        _size = size;
    } 

    public bool IsValid(string value){
        return value != null && value.Length <= _size;
    }
}

public class StringIsNotDigitValidator : IValidator{
    private int _idx;
    
    publiic StringIsNotDigitValidator (int idx){
        _idx = idx;
    } 

    public bool IsValid(string value){
        return value != null && !char.IsDigit(value[_idx]);
    }
}

public class StringAlphanumericValidator : IValidator{
    public bool IsValid(string value){
        return value != null && value..All(c => char.IsLetterOrDigit(c))
    }
}

    
var value = "A string para eu validar";
var validators = new IValidator[]{
    new StringMaximumSizeValidator(100),
    new StringIsNotDigitValidator(0), 
    new StringAlphanumericValidator()
};
var isValid = validators.All(v => v.IsValid(value));

if(!isValid){
  throw new InvalidOperationException("A string não é válida");
}

The advantage is that validation rules are now made through an object. This allows you to better test each of the validation processes. It should also be easier to create new validation rules and use those you want for a particular scenario.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.