Factory Design Pattern

Aplicando Factory Design Pattern em javascript

Fala galera, tudo bem com vocês?
Hoje estarei falando um pouco sobre essa padrão de projetos e dando alguns exemplos para ficar mais claro o objetivo e as vantagens do uso dele nas suas aplicações.

  • O Factory é um padrão de projeto de criação, e foi "definido" pela Gang of Four.
  • Ele foi criado para diminuir a complexidade da criação de objetos.
  • Muitas vezes sem usar esse padrão você acaba tendo código duplicado para criar seus objetos.

Como implementar o Factory design pattern em javascript

Existem diversas formas de implementar esse padrão de projeto no javascript. Alguns usam classes e outros ainda utilizam prototype, aqui estarei demonstrando como implementar o Factory utilizando funções simples.

Veja como é simples a criação de uma Factory

const AnimalFactory = function (nome) {
  const animal = {};
  animal.nome = nome;
  animal.andar = function () {
    // É possivel acessar as propriedades através do this
    // e do próprio objeto animal
    console.log(this.nome + " andou");
  };
  return animal;
};

Com esse código nós acabamos de criar nossa Factory de animais, agora podemos criar diversos animais através dela e utiliza-los dessa forma

const cachorro1 = AnimalFactory("Jamal");
const cachorro2 = AnimalFactory("Ralph");

cachorro1.andar(); // Jamal andou
cachorro2.andar(); // Ralph andou

Ao executar a função Animal é criada e retornada uma nova instancia do objeto animal que foi definido dentro do escopo da função

Como você pode ver nosso Factory não implementa muitas funcionalidades para nossos objetos. Então como nós fazemos para adicionar mais funcionalidades de uma forma inteligente e condicional.

É ai que entra outra coisa muito legal, os Mixins

Se você quiser um conteúdo mais detalhado sobre os Mixins da uma olhada nesse outro post aqui no blog, Functional Mixins, como utilizar

Vamos dizer que a gente queira adicionar funcionalidades no objeto final que será retornado, mas que não seja uma funcionalidade que todos os tipos de animais tem, porque se não seria só adicionar dentro da factory, tipo a função *andar.*

Por exemplo um pássaro, ele pode voar, mas um cachorro não, então é uma funcionalidade condicional. Então vou demonstrar uma forma de fazer isso.

Primeiro vamos criar a funcionalidade de voar, pra isso vamos criar um objeto chamado podeVoar, que será responsável por informar que o animal pode voar. Dentro desse objeto temos a função voar, mas poderíamos ter quantas funções fossem necessárias.

const podeVoar = {
  voar() {
    console.log(`${this.nome} voou`);
    // onde esse this é o objeto retornado pela Factory
    // então temos acesso ao nome e tudo mais
  },
};

Agora se a gente quiser criar um animal que possa voar vamos fazer dessa forma.

const passaro1 = Object.assign(AnimalFactory("Papagaio"), podeVoar);

passaro1.voar(); //Papagaio voou

// A função Object.assign ira copiar a função voar
// de dentro do objeto podeVoar para dentro do objeto 
// que será criado ao executar a Factory.
// Sendo assim dentro do objeto passaro1
// teremos a propriedade nome e as funções
// andar e voar

Para facilitar a criação de animais que podem voar a gente pode criar uma Factory específica pra isso.

const AnimalVoadorFactory = function (nome) {
  // Vou escrever em linhas separadas para ficar mais claro
  // mas poderia ser tudo em uma linha só
  const animal = AnimalFactory(nome);
  const animalVoador = Object.assign(animal, podeVoar);
  return animalVoador;
};

Agora fica mais fácil de criar animais que podem voar. A forma de usar é a mesma da Factory anterior

const passaro2 = AnimalVoadorFactory("Beija‑flor");

passaro2.andar(); // Beija‑flor andou
passaro2.voar(); // Beija‑flor voou

E nós podemos adicionar quantos mixins forem necessários.

Vamos criar uma funcionalidade para que possamos criar animais que nadem.

const podeNadar = {
  nadar() {
    console.log(`${this.nome} nadou`);
  },
};

Se quisermos criar um animal que possa voar e nadar fazemos dessa forma

const passaro3 = Object.assign(AnimalFactory("Gaivota"), podeNadar, podeVoar);

passaro3.andar(); // Gaivota andou
passaro3.voar(); // Gaivota voou
passaro3.nadar(); // Gaivota nadou

Outra coisa que é possível fazer e pode ser bem útil é a utilização de diversas Factories para gerar um objeto complexo. Vamos criar outra Factory para ficar mais claro.

// Para demonstrar uma outra forma de passar os paramentros
// vou fazer uma desestruturação aqui
const PessoaFactory = function ({ nome, idade }) {
  const pessoa = { nome, idade };

  pessoa.falar = function (mensagem) {
    console.log(`${pessoa.nome} falou: ${mensagem}`);
  };

  return pessoa;
};

E para criar um objeto utilizando as duas Factories fazemos dessa forma

// Você pode perceber que aqui não estou passando o atributo
// nome para a Factory de Animal. Não teve problema 
// pois na Factory Pessoa também tem esse atributo
// então nas funções que utilizam o nome vão continuar funcionando
const humano1 = Object.assign(AnimalFactory(), PessoaFactory({nome: "Allison", idade: 24}))

humano1.andar() // Allison andou
humano1.falar("Aprendendo sobre Factory Design Pattern") // Allison falou: Aprendendo sobre Factory Design Pattern

Uma outra forma de utilizar esse padrão é criar uma Factory que gera outras Factories dependendo do tipo

const SerVivoFactory = function ({ tipo, nome, ...outrosAtributos }) {
  switch (tipo) {
    case "animal":
      return AnimalFactory(nome);
      break;
    case "animalVoador":
      return AnimalVoadorFactory(nome);
      break;
    case "humano":
      // Dessa forma não estamos adicionando os dados da Factory AnimalFactory
      return PessoaFactory({ nome, ...outrosAtributos });
      break;
    default:
      // Aqui retornei um erro, mas poderia retornar um valor padrão
      throw new Error("Informe um tipo");
      break;
  }
};

const humano2 = SerVivoFactory({ tipo: "humano", nome: "Fernanda", idade: 22 });
humano2.falar("Olá pessoal!"); // Fernanda falou: Olá pessoal!

const passaro4 = SerVivoFactory({ tipo: "animalVoador", nome: "Pelicano" });
passaro4.voar(); // Pelicano voou

As Factories são bem interessantes, considere usar esse padrão de projeto no seu dia a dia. Agora você tem uma ferramenta a mais nas suas mãos.

Conclusões finais

Com as Factories ficou bem mais simples criar objetos, e adicionando os Mixins a gente consegue adicionar novas funcionalidades nos objetos sem ter que alterar a Factory.

Ao meu ver a principal vantagem do Factory Design Pattern é a ajuda que ele te da para encapsular e centralizar o código de criação de objetos, tornando mais fácil a manutenção.

Espero que você tenha aproveitado alguma coisa do conteúdo apresentado.

Aqui no blog tem posts falando sobre outros Design Patterns, da uma olhada.

Acho que é isso, se tiver ficado com alguma dúvida ou tenha alguma sugestão escreve aqui nos comentários.