Composite - Design Pattern
Tags: designpattern, typescript, refactoring
Published: 2022-10-31 22:15:25

**Problema**
Imagine o seguinte cenário: sua aplicação possui diversas integrações com plataformas de envio de eventos, todas seguindo o mesmo padrão de conteúdo de envio. Sua aplicação começa a ficar repleta de integrações de envio de eventos como por exemplo: Facebook, Firebase Analytics etc. O que nos leva ao seguinte problema:

- Código "redundante", sua aplicação começa a enviar vários eventos com o mesmo padrão de código.
- Se por algum motivo você desejar remover/desligar algum evento específico, ou vários eventos ao mesmo tempo, você terá um árduo caminho pela frente.

Esse foi um dos cenários encontrados ao se ter uma aplicação integrada com vários envios de eventos. Estávamos perdendo o controle 🤯.

**Solução**
Queríamos ter um maior controle sobre os nossos eventos, que vão desde:

- Remover a "redundância" de envio de eventos no código;
- Controlar os eventos enviados, desde um evento específico à um grupo de eventos;

Em conjunto com a equipe, ao se deparar com o problema, percebemos que a solução do problema se encaixava muito bem no **Padrão de Projeto** **Composite**.

## Então como podemos resolver o problema usando o Composite?

Primeiro vamos ver a proposta de solução para o problema através de um **Diagrama de Classes UML**, e assim, discutir um pouco baseando-se nele.


![Composite Diagram of Class](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oiuki6x4o01ikej6wmxb.png)

Vamos lá, primeiro vamos começar resolvendo os nossos problemas passo a passo.

- De inicio queremos poder enviar eventos de **add** e de **view**, através de plataformas como **Facebook**, **Firebase** etc. Para isso criamos uma interface **EventActions** que contém as ações mencionadas, fazendo com que o Facebook e o Firebase respeitem esse _contrato_.

- Porém, como mencionado, queremos poder ter não só eventos por plataformas específicas, mas como também, eventos baseados por _escopo_. Para isso criamos o **Analytics** que também respeita o contrato com EventActions. Se pararmos para pensar, o Facebook e o Firebase tem certa relação com o Analytics por se tratarem de eventos de análise. Só que ainda tem um ponto... Precisamos de alguma forma fazer essa referência entre "pai" e "filho". Vamos resolver esse probleminha no próximo passo.

- Chegamos a mencionar que queríamos poder desabilitar os eventos quando necessário. Para isso criamos a interface **EventStatus**, onde ela contém a função de poder desabilitar o evento, todas as nossas classes possuem um contrato com ela também. Mas não só isso, vamos aproveitar para adicionar a responsabilidade de poder atribuir um pai para a classes que respeitam esse contrato, assim, resolvendo o problema mencionado no passo acima.

- No entanto, tem algo faltando... Comentamos da possibilidade de poder desabilitar os eventos especificamente ou por características/escopo. Para conseguirmos isso, vamos criar um **EventComposite** que tem um contrato com EventStatus e EventActions. Além disso, possui também a característica de conter uma lista de eventos.

Acho que agora ficou um pouco mais claro a solução para o nosso problema.

## Vamos ver como funciona na prática

Seguindo o passo a passo acima, vamos criar as nossas classes e interfaces, conforme diagrama UML.

Primeiro criamos as nossas interfaces que vão definir as regras que as classes devem seguir.
```ts
interface EventActions {
  add(): void
  view(): void
}
```

```ts
interface EventStatus {
  parent?: EventStatus
  readonly isDisabled: boolean

  disable(event?: EventStatus): boolean | void
}
```

Agora, criamos as nossas classes responsáveis pelo envio de eventos.
```ts
class Facebook implements EventActions, EventStatus {
  parent?: EventStatus
  isDisabled: boolean

  disable(event?: EventStatus): boolean {
    this.isDisabled = true
    return this.isDisabled
  }
  add(): void {
    if(!this.isDisabled) {
      console.log('Facebook: add')
    }
  }
  view(): void {
    if(!this.isDisabled) {
      console.log('Facebook: view')
    }
  }
}
```

```ts
class Firebase implements EventActions, EventStatus {
  parent?: EventStatus
  isDisabled: boolean
  
  disable(event?: EventStatus | undefined): boolean {
    this.isDisabled = true
    return this.isDisabled
  }
  add(): void {
    if(!this.isDisabled) {
      console.log('Firebase: add')
    }
  }
  view(): void {
    if(!this.isDisabled) {
      console.log('Firebase: view')
    }
  }
}
```

Aqui temos um caso em particular, como o Analytics não é de fato resposável por enviar eventos, e sim, um tipo mais abrangente, a implementação é mínima.
```ts
class Analytics implements EventActions, EventStatus {
  parent?: EventStatus
  isDisabled: boolean


  disable(event?: EventStatus | undefined): boolean {
    this.isDisabled = true
    return this.isDisabled
  }
  add(): void {}
  view(): void {}
}
```

E aqui temos a cereja do bolo, o nosso EventComposite, responsável por realizar a composição dos nossos eventos.
```ts
type EventUseCase = EventStatus & EventActions

class EventComposite implements EventActions, EventStatus {
  parent?: EventStatus
  isDisabled: boolean

  constructor(private readonly events: Array<EventUseCase>) {}

  disable(event?: EventStatus): boolean | void {
    if(event?.parent === undefined) {
      this.completeForEachEventsWith((eventOfList) => {
        if(eventOfList.parent === event) {
          eventOfList.disable()
        }
      })
    }

    if(event?.parent) {
      event?.disable()
    }
  }
  add(): void {
    this.completeForEachEventsWith((event) => {
      event.add()
    })
  }
  view(): void {
    this.completeForEachEventsWith((event) => {
      event.view()
    })
  }

  private completeForEachEventsWith(complete: (event: EventUseCase) => void) {
    this.events.forEach((event) => {
      complete(event)
    })
  }
}
```

Agora vamos brincar um pouco. Aqui criamos os nosso eventos e adicionamos ao nosso composite. Preste atenção quando atribuímos um pai para o facebook e firebase.
```ts
const main = () => {
  const analytics = new Analytics();
  const facebook = new Facebook()
  facebook.parent = analytics
  const firebase = new Firebase()
  firebase.parent = analytics

  const composite = new EventComposite([analytics, facebook, firebase])
}
```

Vamos desabilitar primeiro o facebook e executar o view e add do composite.
```ts
 composite.disable(facebook)

 composite.view()
 composite.add()
```

**Saída:**
```
"Firebase: view" 
"Firebase: add" 
```

Agora vamos desabilitar somente o analytics.
```ts
 composite.disable(analytics)

 composite.view()
 composite.add()
```

**Saída:**
```
```

Você pode estar se perguntando, o que aconteceu com a saída? Como o analytics é pai do facebook e firebase, ao desabilitarmos ele, automaticamente desabilitamos os seus filhos. Eis a mágica do **Composite** 🤌🏻.

Como podemos ver, o Composite é uma boa solução para resolver problemas onde precisamos compor os objetos em um formato de árvore e conseguir tratá-los de forma singular.

Aqui você encontrará o repo do código utilizado como exemplo: https://github.com/MarlonBeloMarques/composite-designpattern


É isso galera, até a próxima.