Construindo uma 'Method Chain' em JavaScript

← ← ←   8/16/2022, 11:28:50 PM | Posted by: Felippe Regazio


Primeiro vamos as definições: Method Chaining (Métodos Encadeados) envolve dois importantes design patterns:

Vc pode pesquisar mais sobre esses termos se quiser se aprofundar. Você também pode procurar sobre method chain pesquisando por "The builder" design pattern.

Uma Method chain é definida por: métodos que podem ser invocados linearmente sem a necessidade de um objeto/valor intermediário. jQuery é um exemplo

  
    $(...).height().scrollTo().remove()...
  

O objeto vai sendo criado (creational pattern) usando uma corrente de ações (method chain)

Em JS, para criar nossa Method Chain temos que seguir dois princípios simples:

  1. A corrente precisa compartilhar o mesmo contexto
  2. Todo método deve sempre retornar o contexto

Na prática isso fica assim:

  
    class Person {
      constructor(bodyParts) {
        this.bodyParts = bodyParts;
      }
    
      addHead(n) {
        this.bodyParts.head = n;
        return this;
      }
    
      addLegs(n) {
        this.bodyParts.legs = n;
        return this;
      }
    
      addArms(n) {
        this.bodyParts.arms = n;
        return this;
      }
    }    
  

Veja que recebemos um objeto inicializador (bodyParts) e definimos ele como uma propriedade nossa.

Depois criamos nossa chain: cada metodo manipula a propriedade bodyParts e depois retorna o contexto inteiro de volta (this). Eis a chain:

Você pode usar assim:

  
    // simple usage
    const henry = new Person().addHead(1).addArms(2).addLegs(2);

    // initializing with object
    const mia = new Person({ head: 1 }).addArms(2).addLegs(2);

    // creatign an alien
    const alien = new Person().addHead(2).addArms(4).addLegs(6);
  

E o resultado seria esse. Veja que cada "Pessoa" foi construída a partir da nossa chain, e o resultado foi a composição de um objeto utilizando creational pattern.

  
    const henry = new Person().addHead(1).addArms(2).addLegs(2);
    // { head: 1, arms: 2, legs: 2 }

    const mia = new Person({ head: 1 }).addArms(2).addLegs(2);
    // { head: 1, arms: 2, legs: 2 }

    const alien = new Person().addHead(2).addArms(4).addLegs(6);
    // { head: 2, arms: 4, legs: 6 }
  

Essa mesma técnica poderia ser usada utilizando apenas notação de função (you might not need classes in javascript) da seguinte maneira:

  
    function Person(bodyParts = {}) {
      return {
        bodyParts,
    
        addHead(n) {
          this.bodyParts.head = n;
          return this;
        },
      
        addLegs(n) {
          this.bodyParts.legs = n;
          return this;
        },
      
        addArms(n) {
          this.bodyParts.arms = n;
          return this;
        },
      }
    }
  

Você pode usar a função como construtora:

  
    const henry = new Person({ ... }).addLegs(n).addArms(n)...
  

Ou com notação de função mesmo:

  
    const mia = Person({ ... }).addLegs(n).addArms(n)...
  

Ambos terão o getter:

  
    henry.bodyParts // { arms, legs }
    mia.bodyParts // { arms, legs }
  

De qualquer forma, se vc é mais perfecionista, deve observar que ao retornar um objeto como sendo uma chain faz com que ele venha "sujo" de metodos herdados da prototype e também tenha em seu retorno seus metodos expostos. Se você serializasse o valor gerado poderia ter problemas:

  
    const p = new Person();
    // {bodyParts: {}, addHead: ƒ, addLegs: ƒ, addArms: ƒ}
  

Podemos evitar essa "sujeira" fazendo com que nossa chain seja na verdade o prototype do nosso objeto manipulado:

  
    function Person(bodyParts = {}) {
      const chain = Object.create({
        addHead(n) {
          this.bodyParts.head = n;
          return this;
        },
      
        addLegs(n) {
          this.bodyParts.legs = n;
          return this;
        },
      
        addArms(n) {
          this.bodyParts.arms = n;
          return this;
        },
      });
    
      chain.bodyParts = bodyParts;
      return chain;
    }    
  

Da forma acima assinamos a nossa chain como um objeto tendo nossos metodos como prototype. E após isso assimos ao objeto gerado a propriedade bodyParts. Agora:

  
    const p = new Person();
    // {bodyParts: {}}
  

Parabéns, você acaba de aprender o "Method Chaining Design Pattern". Eu usei exemplos bem dummy pra tentar simplificar a explicação, mas com um pouquinho de tecnica e criatividade vc consegue fazer variações e obter resultados muito legais com ele.