Refatorando Ifs Aninhados com Chain of Responsibility
Tags: designpatterns, typescript, tutorial, javascript
Published: 2023-01-22 16:00:00

## Funções ideais

Num mundo ideal funções são pequenas, muito bem definidas, possuem uma única responsabilidade e não crescem, mas no mundo real isso não é uma realidade, funções tendem a crescer, mesmo que tenham uma única função de fato. 

Imagine uma função de uma aplicação veterinária que deve dizer ao usuário qual comida é adequada para um determinado animal, de forma simples a função seria mais ou menos dessa forma:
```ts
function properFoodForAnimal(anAnimal: string): string {
  let food;

  if ("dog" === anAnimal.toLowerCase()) food = "beef meat";

  if ("cat" === anAnimal.toLowerCase()) food = "fish meat";

  if ("owl" === anAnimal.toLowerCase()) food = "rat meat";

  if ("monkey" === anAnimal.toLowerCase()) food = "banana";

  if ("horse" === anAnimal.toLowerCase()) food = "corn";

  return `Animal ${anAnimal} will eat ${food}`;
}
```
Note que a medida em que a veterinária "expandir o seu mercado" e novos animais forem atendidos essa função tende a crescer ganhando novos `ifs`. 
Para evitar que isso aconteça podemos aplicar o design pattern _chain of responsibility_.

## Chain of Responsibility

É um padrão de projeto comportamental muito utilizado quando temos uma cadeia (_chain_) de situações que lidam com o mesmo escopo como a escolha de uma comida, calculo de um preço, filtragem de campos dentre outras. 
Esse padrão permite que vários objetos manipulem a solicitação sem que haja acoplamento entre o trecho de código que envia a requisição e seus receptores. Podemos facilmente identificar esse padrão no nosso código através da interface a qual a solução é implementada.

```ts
interface Handler {
    setNext(handler: Handler): Handler;

    handle(request: string): string;
}
```
## Procedimento

Sempre que vamos começar a refatorar um trecho de código temos de nos certificar que aquele trecho possui uma suite de testes robusta que vai nos assegurar de não causar nenhum bug, então a primeira coisa que vamos fazer é justamente escrever os testes para a função.

```ts
describe("Proper food for animal", () => {
  it("should return the correct phrase containing the proper for a dog", () => {
    let phrase = properFoodForAnimal("dog");

    expect(phrase).toBe("Animal dog will eat beef meat");
  });

  it("should return the correct phrase containing the proper for a cat", () => {
    let phrase = properFoodForAnimal("cat");

    expect(phrase).toBe("Animal cat will eat fish meat");
  });

  it("should return the correct phrase containing the proper for a owl", () => {
    let phrase = properFoodForAnimal("owl");

    expect(phrase).toBe("Animal owl will eat rat meat");
  });

  it("should return the correct phrase containing the proper for a monkey", () => {
    let phrase = properFoodForAnimal("monkey");

    expect(phrase).toBe("Animal monkey will eat banana");
  });

  it("should return the correct phrase containing the proper for a horse", () => {
    let phrase = properFoodForAnimal("horse");

    expect(phrase).toBe("Animal horse will eat corn");
  });
});
```
Escritos os testes iniciamos a refatoração, primeiramente criando a interface `AnimalFoodHandler` que é nada além da interface genérica do pattern apresentada anteriormente.

```ts
interface ProperFoodHandler {
  setNext(handler: ProperFoodHandler): ProperFoodHandler;

  handle(anAnimal: string): string;
}
```
Pode parecer desnecessário a primeira vista nomear essa interface de forma tão específica uma vez que a proposta é que ela seja genérica, mas isso impede outros programadores de aplicar handlers não adequados para aquela situação já que o intellisense do ts avisará que os objetos não são do mesmo tipo.
O próximo passo então é criarmos as classes handlers para cada animal que temos no código hoje, como todo handler possui a mesma estrutura e lida com o mesmo domínio podemos fazer o uso de herança e polimorfismo para deixar o nosso código ainda mais claro. Vamos criar uma classe abstrata de handler da qual se derivarão as classes filhas.

```ts
export class AbstractFoodHandler implements IFoodHandler {
  private nextHandler: IFoodHandler | undefined;

  next(handler: IFoodHandler): IFoodHandler {
    this.nextHandler = handler;

    return handler;
  }

  handle(anAnimal: string): string | null {
    if (this.nextHandler) {
      return this.nextHandler.handle(anAnimal);
    }

    return null;
  }
}
```
Agora podemos ter handlers concretos derivado da classe abstrata de forma com que haja um handler para cada situação, assim sendo temos

`DogFoodHandler.ts`

```ts
export class DogFoodHandler extends AbstractFoodHandler {
  handle(anAnimal: string): string | null {
    if ("dog" === anAnimal.toLowerCase())
      return `Animal dog will eat beef meat`;

    return super.handle(anAnimal);
  }
}
```

`CatFoodHandler.ts`

```ts
export class CatFoodHandler extends AbstractFoodHandler {
  handle(anAnimal: string): string | null {
    if ("cat" === anAnimal.toLowerCase())
      return `Animal cat will eat fish meat`;

    return super.handle(anAnimal);
  }
}
```

`OwlFoodHandler.ts`

```ts
export class OwlFoodHandler extends AbstractFoodHandler {
  handle(anAnimal: string): string | null {
    if ("owl" === anAnimal) return `Animal owl will eat rat meat`;

    return super.handle(anAnimal);
  }
}
```

`MonkeyFoodHandler.ts`

```ts
export class MonkeyFoodHandler extends AbstractFoodHandler {
  handle(anAnimal: string): string | null {
    if ("monkey" === anAnimal.toLowerCase())
      return `Animal monkey will eat banana`;

    return super.handle(anAnimal);
  }
}
```

`HorseFoodHandler.ts`

```ts
export class HorseFoodHandler extends AbstractFoodHandler {
  handle(anAnimal: string): string | null {
    if ("horse" === anAnimal.toLowerCase()) return `Animal horse will eat corn`;

    return super.handle(anAnimal);
  }
}
```

Ainda podemos criar um arquivo `index.ts` para facilitar a exportação de arquivos.

```ts
import { CatFoodHandler } from "./CatFoodHandler";
import { DogFoodHandler } from "./DogFoodHandler";
import { MonkeyFoodHandler } from "./MonkeyFoodHandler";
import { OwlFoodHandler } from "./OwlFoodHandler";
import { HorseFoodHandler } from "./HorseFoodHandler";

const catHandler = new CatFoodHandler();
const monkeyHandler = new MonkeyFoodHandler();
const owlHandler = new OwlFoodHandler();
const horseHandler = new HorseFoodHandler();

const handler = new DogFoodHandler();

handler
  .next(catHandler)
  .next(owlHandler)
  .next(monkeyHandler)
  .next(horseHandler);

export { handler };
```

O código final da nosssa aplicação ficará assim:

```ts
export function properFoodForAnimal(anAnimal: string): string | null {
  return handler.handle(anAnimal);
}
```

Assim, se quisermos adicionar uma nova condição dentro desse fluxo de código basta criarmos uma nova classe concreta de Handler, inicializá-la e colocá-la na cadeia dentro do arquivo `index.ts`. 

[Código fonte](https://github.com/abelsouzacosta/chain-of-responsibility-example)