Strategy: Uma perspectiva Head First
Tags: programming, designpatterns, strategy, beginners
Published: 2023-12-30 04:13:42

Olá pessoas, através desse post eu estou dando inicio à **uma série** onde eu explico um pouco sobre os **principais _design patterns_** usados no mercado de uma forma que seja de fácil entendimento à qualquer desenvolvedor, então espero que gostem :)

Minha principal fonte será o livro [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://www.amazon.com.br/Head-First-Design-Patterns-Object-Oriented/dp/149207800X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=), que inclusive é de onde eu tirei o título para esse post, então meu objetivo aqui é fazer um **resumo** dos principais pontos que o livro trata e o que podemos aprender com tudo isso. Caso tenha interesse, recomendo muito lê-lo, ele aborda os padrões de projeto de uma forma muito didática e divertida.

Os design patterns são como ferramentas no cinto de um carpinteiro, **estratégias testadas e aprovadas para lidar com problemas específicos**. Em vez de reinventar a roda a cada projeto, podemos aprender com as **melhores práticas** consolidadas pela comunidade de desenvolvimento.

Primeiramente, a cada pattern explicado, irei colocar a lista dos **Princípios de Design de código** que são citados no livro, pois eles que **permeiam todo o conceito dos _Design Patterns_**. Além disso, irei demonstrar com qual(is) desses princípios o padrão atual se relaciona.

---

<center><h2>Princípios de design de código</h2></center>
* Encapsule o que varia.
* Prefira composição à herança.
* Programe para interfaces, não implementações.
* Se esforce em usar designs que tenham fraco acoplamento entre objetos que interagem entre si.
* Princípio do menor conhecimento: Apenas fale com seus amigos.
* Princípio de Hollywood: Não nos chame, nós chamamos você.
* Uma classe deve haver apenas uma razão para mudar.

---

<center><h2>Strategy</h2></center>

### Definição

O padrão de projeto _Strategy_ **define uma família de algoritmos**, **encapsula** cada um deles, e os permitem serem **intercambiáveis** (trocados) em **tempo de execução**. O padrão deixa o algoritmo **variar independentemente dos clientes** que o usam.

### Problemática - Jogo de Patos

Esse é um dos exemplos mais clássicos ao se falar de _Strategy_, mas é bem didático e fácil de entender, então vamos lá.

Vamos imaginar que estamos desenvolvendo um jogo onde o usuário pode controlar um **pato**. Esse pato pode **nadar** e fazer **quack**. Então, vamos criar uma classe `Duck` que será a classe base para todos os patos do jogo. Essa classe terá os métodos `swim()`, `quack()` e `display()`, que mostra o pato na tela.

```java
public abstract class Duck {
    public void swim() {
        System.out.println("I'm swimming!");
    }

    public void quack() {
        System.out.println("Quack!");
    }

    // Cada pato tem que sobreescrever o método
    public abstract void display();
}
```

Também precisamos dos patos em si, então vamos criar as classes `MallardDuck` e `RedheadDuck` que herdam de `Duck`.

![Diagrama de classe dos patos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4bvlhaq2mmg6yawkngu.png)

Mas, após um tempo, decidiram que o jogo precisava de **patos voadores**.
Qual é o primeira solução que vem à mente? "Ah, vou criar um método `fly()` na classe `Duck` e pronto, agora os patos podem voar!".

Então ok, vamos adicionar o método `fly()`.

![Diagrama de classes com método fly()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6oaog4rqccf61oeswh9.png)

#### Mas temos um problema...

Depois de um tempo, decidiram que o jogo precisava de **patos de borracha**. E agora? **Patos de borracha não voam**, então não faz sentido eles terem o método `fly()`. O que faremos? Vamos criar uma classe `RubberDuck` que herda de `Duck` e sobrescreve o método `fly()` para não fazer nada? Isso pode até funcionar para esse caso, mas e se precisarem de outro tipo de pato que **não voa e nem faz quack** (um pato de madeira, por exemplo)? **Iremos sobrescrever todos os métodos que não fazem sentido para ele e deixar como nulo?** Dessa forma só estamos poluindo nossas classes e não estamos utilizando a herança de maneira efetiva.

![Pato de Borracha voador?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uknoydz944w446fcfzfu.png)

#### E agora?

E agora temos um problema, pois esse é apenas um dos casos em que o design aplicado não é flexível o suficiente para lidar com qualquer tipo de pato. Podem haver outros tipos de patos que precisem de outros métodos diferentes, ou que precisem apenas parcialmente dos métodos. 
Então precisamos pensar em uma forma de **deixar o código mais flexível**.

#### Problemas na utilização de herança nos seus designs

Uma das primeiras coisas que a gente aprende na faculdade/técnico, é a chamada **herança**. Ela é um dos pilares da Programação Orientada a Objetos, porém, em sua grande maioria, **não é a melhor solução para os problemas que encontramos no dia a dia**. Ela nos deixa preso à classe pai, então sempre que pensar em usar herança, pense duas vezes, pois ela pode não ser a melhor solução.

**Algumas das desvantagens da herança no nosso exemplo são:**

- **Herança é definida tempo de compilação:** Isso significa que **não podemos alterar o comportamento** de um objeto em tempo de execução, pois ele já foi definido em tempo de compilação.
- Mudanças podem afetar outros patos sem querer.

Isso não quer dizer que a herança seja sempre ruim, mas em quase todos os Design Patterns que veremos nessa série, usamos de **composição** ao invés de herança, pois ela nos dá **mais flexibilidade e nos permite alterar o comportamento dos objetos em tempo de execução**.

### Solução - _Strategy_

Antes de irmos para a solução, lembram do primeiro princípio de design? "**Encapsule o que varia**", ou mais especificamente, "Identifique os aspectos da sua aplicação que variem e os separe daquilo que permanece o mesmo". Então, iremos usar dele como base para nossa solução.

Bom, o que varia? O comportamento dos patos, tais como **voar** e **quack**. Então, vamos encapsular esses comportamentos em **classes separadas** e fazer com que os patos tenham uma referência para esses comportamentos. Dessa forma, podemos **alterá-los em tempo de execução**.

Para separarmos os comportamentos, usaremos o terceiro princípio de design: "**Programe para interfaces, não implementações**". Pois dessa forma, cada tipo de comportamento implementará sua respectiva interface, sendo possível mudar entre implementações a qualquer momento, já que o pato só precisa saber que ele tem um comportamento e não como ele funciona.

**Obs:** Lembrando que "interfaces" no nosso contexto **não significa necessariamente apenas interfaces** das linguagens orientadas a objetos, mas sim, **qualquer supertipo que nos permita referenciá-lo sem precisar saber qual é o o tipo exato do objeto**. Em java, por exemplo: Classe abstrata, interfaces, etc.

#### Interface FlyBehavior

Interface que será usada para definir o tipo de vôo do pato, para cada tipo de vôo, uma classe nova será criada.

![Diagrama Interface FlyBehavior](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kjpmiytpn3dp1lgdn04j.png)

#### Interface QuackBehavior

Interface que será usada para definir o tipo de "quack" do pato, para cada tipo de quack, uma classe nova será criada.

![Diagrama Interface QuackBehavior](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggoa4gutt0ku3c4zv3me.png)

### Como integrar isso com nossa classe Duck?

O segredo aqui é fazer a **classe `Duck` delegar seus comportamentos** de voar e fazer quack para as interfaces criadas acima.

**Então, seguiremos alguns passos na classe `Duck`:**

1. Adicionar dois atributos (do tipo interface) que armazenam ambos comportamentos.
1. Trocar métodos `quack()` e `fly()` para novos que apenas chamam o `quack()` e `fly()` dos comportamentos.

![Diagrama de classe Duck após strategy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6457us3k16gr1n4dhxq.png)

```java
public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public Duck() {}

    public abstract void display();

    public void performFly() {
        flyBehavior.fly(); // Delegou para o flyBehavior
    }

    public void performQuack() {
        quackBehavior.quack(); // Delegou para o quackBehavior
    }

    public void swim() {
        System.out.println("Nadar");
    }
}
```

#### Classes após Strategy

```java
public class FlyWithWings implements FlyBehavior {
   @Override
   public void fly() {
      System.out.println("FlyWithWings (voa com asas)");
   }
}
```

```java
public class FlyNoWay implements FlyBehavior {
   @Override
   public void fly() {
      System.out.println("FlyNoWay (não consegue voar)");
   }
}
```

```java
public class Quack implements QuackBehavior {
   @Override
   public void quack() {
      System.out.println("Quack");
   }
}
```

```java
public class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    public void display() {
        System.out.println("MallardDuck");
    }
}
```

Agora, **para cada tipo de pato**, nós podemos simplesmente **extender a classe `Duck`** e setar de alguma forma **qual o comportamento que será usado**. Aqui estamos setando através do construtor, mas poderia ser de outra forma também.

### Como deixar os comportamentos dinâmicos em tempo de execução?

Um dos propósitos do Strategy é ter a flexibilidade da implementação ser trocada em tempo de execução, e para isso, na nossa classe `Duck`, devemos adicionar dois **métodos setter**: `setFlyBehavior()` e `setQuackBehavior()`.

```java
public abstract class Duck {
    // ... Outros atributos e métodos
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}
```

### Hora de testarmos nosso "Jogo de patos"

```java
public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performQuack(); // Quack
    mallard.performFly(); // FlyWithWings (voa com asas)

    mallard.setFlyBehavior(new FlyNoWay());
    mallard.performFly(); // FlyNoWay (não consegue voar)
}
```

Percebem que agora nossa aplicação ficou muito mais flexível e muito mais fácil de manter?
Caso a gente precise de um novo tipo de vôo, **basta criarmos uma nova classe que implementa a interface FlyBehavior**. Mesma coisa com um novo tipo de quack. O que terá que mudar será o código cliente, que deve conhecer os novos comportamentos para poder escolher o necessário.

### Quando usar Strategy?

A escolha de utilizar o padrão de projeto Strategy é especialmente benéfica em diversas situações no desenvolvimento de software. Aqui estão alguns cenários em que o Strategy brilha e pode ser a solução ideal:

**Comportamentos Variáveis:** Utilize o Strategy quando você tem algoritmos ou comportamentos que podem variar e precisam ser escolhidos dinamicamente em tempo de execução. Isso proporciona uma flexibilidade significativa, permitindo que você adapte o comportamento do sistema sem modificar diretamente suas classes.

**Evitar Herança Excessiva:** Se você perceber que está criando uma hierarquia de classes com muitas subclasses apenas para fornecer diferentes implementações de um método, o Strategy oferece uma alternativa mais flexível e fácil de manter. Evite a armadilha da herança excessiva e opte pela composição.

**Reutilização de Comportamentos:** Quando você deseja reutilizar comportamentos específicos em diferentes partes do sistema, o Strategy permite encapsular esses comportamentos em classes independentes, promovendo a reutilização de código.

**Manutenção e Extensibilidade:** Use o Strategy para criar sistemas mais fáceis de manter e estender. A capacidade de adicionar novos comportamentos sem alterar o código existente é uma grande vantagem em projetos que exigem evolução contínua.

**Testabilidade:** O Strategy facilita a realização de testes unitários, pois você pode testar cada estratégia separadamente. Isso melhora a testabilidade do código e ajuda a garantir a robustez do sistema.

### Strategy X Programação Funcional

Atualmente, diversas linguagens suportam programação funcional, e por conta da flexibilidade desse paradigma, em vários casos onde normalmente seria usado Strategy, pode ser usada a programação funcional no lugar, já que ela tem um efeito semelhante e elimina a necessidade de criar classes adicionais.

No nosso exemplo dos patos, ao invés de criarmos várias implementações de comportamentos, nós poderíamos passar o comportamento como parâmetro em uma interface funcional do Java.

```java
public abstract class Duck {
	protected Supplier<String> flyBehavior;
	protected Supplier<String> quackBehavior;

	public void performFly() {
		System.out.println(flyBehavior.get());
	}

	public void performQuack() {
		System.out.println(quackBehavior.get());
	}

	public void setFlyBehavior(Supplier<String> flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public void setQuackBehavior(Supplier<String> quackBehavior) {
		this.quackBehavior = quackBehavior;
	}
}

public class MallardDuck extends Duck {
	public MallardDuck() {
		quackBehavior = () -> "Quack";
		flyBehavior = () -> "FlyWithWings (voa com asas)";
	}

	public void display() {
		System.out.println("MallardDuck");
	}
}

public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performQuack(); // Quack
    mallard.performFly(); // FlyWithWings (voa com asas)

    mallard.setFlyBehavior(() -> "FlyNoWay (não consegue voar)");
    mallard.performFly(); /// FlyNoWay (não consegue voar)
}
```

A escolha entre usar o strategy ou programação funcional depende dos requisitos do seus sistema. Se são poucos comportamentos simples e raramente eles mudam, usar programação funcional é uma possibilidade. Agora se são comportamentos mais complexos, ou que podem ser reutilizados em outros locais, o strategy pode se adequar melhor.

É isso pessoal, espero que tenham aprendido algo de novo :) até o próximo pattern! (spoiler: Iremos falar sobre Observer)

### Github com o código-fonte

Toda implementação de código você encontra no meu [GitHub](https://github.com/LeonMarqs/java-design-patterns/tree/main), lá você pode ver e testar o código por conta própria.

### Referências

Deixo aqui algumas referências que usei para escrever esse post e também outros sites muito bons que explicam sobre os _Design Patterns_:

- [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://www.amazon.com.br/Head-First-Design-Patterns-Object-Oriented/dp/149207800X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=)
- [Refactoring Guru](https://refactoring.guru/design-patterns/catalog)