O grande e famoso padrão de projetos **Strategy**! Tão falado e tão ouvido... mas afinal, como funciona?
Se você está iniciando neste mundo de padrões de projeto e qualidade de código e ainda não ouviu falar sobre este padrão, não se preocupe. No decorrer deste artigo, eu vou tentar **descomplicá-lo** e **convencer você** a utilizar ele com mais frequência na sua rotina.
[TOC]
## 1. Padrões de projeto
Antes de mais nada, acho fundamental estabelecermos uma base para quem ainda está começando: afinal, o que são "padrões de projeto"?
Como a **GoF** (Gangue dos Quatros) descreve em seu livro **Padrões de Projeto**, "*[...] um padrão de projeto nomeia, abstrai e identifica os aspectos-chave de uma estrutura de projeto comum para torná-la útil para a criação de um projeto orientado a objetos reutilizável [...]*".
"*E o que isso significa?*" ─ Você pode estar se perguntando.
É bem mais simples do que parece: padrões de projeto, como o nome sugere, são **padrões que podem ser reutilizados em outros sistemas**, pois se trata de uma abstração de uma ideia; um padrão que foi identificado dentro do código. Também é amplamente conhecido por simplesmente ser uma forma de catalogar soluções a problemas comuns.
Se é o seu primeiro contato com padrões de projeto, a ideia pode parecer meio abstrata demais, mas vamos seguir com a prática para tentar facilitar o entendimento.
Especificamente sobre o padrão de projetos **Strategy**, este padrão visa facilitar a manutenibilidade e tornar a potencial expansão do seu código de forma mais simples e objetiva. Isso é possível através da descentralização da responsabilidade da manipulação de um objeto, abstraído por uma interface e suas diferentes "estratégias" de implementações.
## 2. O Strategy
Pulando do teórico e indo direto para o prático, vamos visualizar um problema, refletir um pouco, e então partir direto para uma abordarem de implementação do Strategy.
### 2.1. O Problema
Imagine que exista um objeto **Funcionario** com suas propriedades, e uma classe **CalculadoraSalarial** que realiza o reajuste salarial deste funcionário de acordo com sua performance durante o ano. A performance do funcionário é medida por um ENUM que existe dentro das propriedades do mesmo.
```mermaid
classDiagram
Funcionario <-- Performance
CalculadoraSalarial <-- Funcionario
class Performance {
NEGATIVA,
POSITIVA
}
class Funcionario {
Performance performance
Double salario
...
getPerformance()
getSalario()
setSalario()
...()
}
class CalculadoraSalarial {
reajustaSalario(Funcionario f)
}
```
Agora, imaginaremos as seguintes regras de negócio:
- Se o funcionário tiver uma performance **NEGATIVA** no momento do cálculo, ele **não deve receber reajuste salarial**.
- Se o funcionário tiver uma performance **POSITIVA** no momento do cálculo, seu **salário deve receber um reajuste de 5%**.
Devido a lógica por trás do negócio, não faz sentido retornar um valor de reajuste "x" por padrão. Assim, também criamos uma exceção caso no futuro exista algum novo tipo de performance que esquecemos de implementar na nossa calculadora.
Implementando o método **reajustaSalario** (*no exemplo, utilizando Java*), iremos chegar a um resultado parecido com o seguinte:
```java=
class CalculadoraSalarial {
public void reajustaSalario(Funcionario funcionario) {
Double reajuste = null;
if (funcionario.getPerformance().equals(Performance.NEGATIVA)) {
reajuste = 0d;
} else if (funcionario.getPerformance().equals(Performance.POSITIVA)) {
reajuste = 0.05d;
}
if (reajuste == null) {
throw new RuntimeException("Tipo de performance não implementada.");
}
Double salarioAntigo = funcionario.getSalario();
funcionario.setSalario(salarioAntigo + (salarioAntigo * reajuste));
}
}
```
O código está funcional. Entretanto, uma semana após a implementação, suponhamos que foi solicitado a criação de um novo tipo de performance: **EXCELENTE**. O funcionário que possuir a performance "excelente" recebe **10% de reajuste salarial**.
Sendo simples e direto, o ENUM seria implementado com mais um tipo, o "EXCELENTE". Por último, a implementação do método **reajustaSalario** ficaria algo como:
```java=
class CalculadoraSalarial {
public void reajustaSalario(Funcionario funcionario) {
Double reajuste = null;
if (funcionario.getPerformance().equals(Performance.NEGATIVA)) {
reajuste = 0d;
} else if (funcionario.getPerformance().equals(Performance.POSITIVA)) {
reajuste = 0.05d;
} else if (funcionario.getPerformance().equals(Performance.EXCELENTE)) {
reajuste = 0.10d;
}
if (reajuste == null) {
throw new RuntimeException("Tipo de performance não implementada.");
}
Double salarioAntigo = funcionario.getSalario();
funcionario.setSalario(salarioAntigo + (salarioAntigo * reajuste));
}
}
```
Em cenários como este ─ mas não limitado à ─, onde o código pode facilmente se expandir criando uma corrente de IFs, o padrão Strategy pode ser aplicado. No exemplo acima estamos "pegando leve" ─ no mundo real, é muito mais comum encontrar IFs encadeados, muitas vezes em situações piores do que o nosso. Tudo isso prejudica legibilidade do código como um todo, e no geral, não é uma boa prática.
### 2.2. A solução
"*Certo, e como podemos aplicar o Strategy nesse caso?*" ─ Você deve estar se perguntando. E antes mesmo de responder, já adianto: **não existe uma única forma de resolver esse tipo de problema**.
Existem diversas formas de se resolver um problema com Strategy, a maioria deles envolvendo a implementação de uma interface, ou de uma abstração. No nosso caso, as coisas são bem mais simples graças ao nosso ENUM.
Provavelmente, até agora, a nossa implementação de **Performance** foi a seguinte:
```java=
enum Performance {
NEGATIVA,
POSITIVA,
EXCELENTE;
}
```
Através de abstração, como já mencionado, facilmente podemos resolver o nosso problema nesse caso. Ao implementarmos um método abstrato dentro do nosso ENUM, todos os diferentes tipos dentro dele **exigirão uma implementação**, causando erro em tempo de compilação caso essa exigência não seja atendida, obrigando o desenvolvedor a implementar o método.
```java=
enum Performance {
NEGATIVA {
public Double retornaReajusteSalarial() {
return 0d;
}
},
POSITIVA {
public Double retornaReajusteSalarial() {
return 0.05d;
}
},
EXCELENTE {
public Double retornaReajusteSalarial() {
return 0.10d;
}
};
public abstract Double retornaReajusteSalarial();
}
```
E assim, podemos alterar a forma que a nossa calculadora retorna o reajuste salarial do funcionário, facilitando imensamente a leitura do código, tornando mais simples a manutenção do mesmo, e claro, seguindo os princípios SOLID. Basicamente, contanto que a regra de negócio fundamental não mude, não precisamos mais sequer tocar na nossa "Calculadora".
```java=
class CalculadoraSalarial {
public void reajustaSalario(Funcionario funcionario) {
Double reajuste = funcionario.getPerformance().retornaReajusteSalarial();
Double salarioAntigo = funcionario.getSalario();
funcionario.setSalario(salarioAntigo + (salarioAntigo * reajuste))
}
}
```
Como temos certeza de que o método **retornaReajusteSalarial** possui sua devida implementação dependendo do tipo de performance, garantimos também que não exista o cenário descrito anteriormente, onde "o desenvolvedor esqueceu de implementar o cálculo do reajuste".
*Reitero: essa não é a única forma de implementar o padrão de projeto Strategy, mas acho que é um jeito bem bacana de exemplificar como é bem mais fácil de implementá-lo no nosso código do que geralmente é pensado.*
## 3. Opinião do Autor
O padrão de projetos Strategy, por mais que seja diversas vezes confundido por parecer complexo, é bem mais simples de ser implementado do que parece, e poderia muito bem ser mais visto no código de vários sistemas orientados a objeto.
Às vezes, durante o desenvolvimento, podemos erroneamente assumir que tentar aplicar Design Patterns consome muito tempo. **Isso está errado**. Design Patterns, uma vez conhecidos e dominados, eles só têm a agregar para a velocidade e elegância da sua solução. Padrões de projeto facilitam o desenvolvimento e tornam a manutenibilidade e evolução do código mais simples.