Artigo

Design Patterns - Prototype

Rodrigo Branas
Rodrigo Branas
08 jan 2024·3 min de leitura
#design-patterns#prototype

Instanciar objetos pode ser complexo, exigindo numerosos parametros e as vezes design patterns como Builder. O padrao Prototype permite criar novos objetos clonando instancias existentes e modificando propriedades especificas -- uma abordagem eficiente para criar instancias similares.

Exemplo: Criação de Formularios Dinamicos

Considere uma ferramenta para criar formularios dinamicos (como Google Forms) com duas classes:

class Field {
  fieldId: string;

  constructor(
    public type: string,
    public title: string
  ) {
    this.fieldId = crypto.randomUUID();
  }
}

class Form {
  formId: string;
  fields: Field[];

  constructor(
    public category: string,
    public description: string
  ) {
    this.formId = crypto.randomUUID();
    this.fields = [];
  }

  addField(type: string, title: string) {
    this.fields.push(new Field(type, title));
  }

  removeField(fieldId: string) {
    const index = this.fields.findIndex(field => field.fieldId === fieldId);
    this.fields.splice(index, 1);
  }
}

interface FormRepository {
  save(form: Form): Promise<void>;
  get(formId: string): Promise<Form>;
}

Implementação Inicial do CopyForm

Quando usuários querem copiar formularios existentes, um caso de uso CopyForm lida com isso:

class CopyForm {
  constructor(readonly formRepository: FormRepository) {}

  async execute(input: Input): Promise<void> {
    const form = await this.formRepository.get(input.fromFormId);
    const newForm = new Form(input.category, input.description);
    for (const field of form.fields) {
      newForm.addField(field.type, field.title);
    }
    await this.formRepository.save(newForm);
  }
}

type Input = {
  fromFormId: string;
  category: string;
  description: string;
};

Essa abordagem cria acoplamento desnecessario com as propriedades do objeto e requer iterar pelos campos individualmente.

Implementando o Padrao Prototype

Crie um metodo clone herdado de uma interface Prototype:

interface Prototype {
  clone(): Prototype;
}

class Field implements Prototype {
  fieldId: string;

  constructor(
    public type: string,
    public title: string
  ) {
    this.fieldId = crypto.randomUUID();
  }

  clone(): Field {
    return new Field(this.type, this.title);
  }
}

class Form implements Prototype {
  formId: string;
  fields: Field[];

  constructor(
    public category: string,
    public description: string
  ) {
    this.formId = crypto.randomUUID();
    this.fields = [];
  }

  addField(type: string, title: string) {
    this.fields.push(new Field(type, title));
  }

  removeField(fieldId: string) {
    const index = this.fields.findIndex(field => field.fieldId === fieldId);
    this.fields.splice(index, 1);
  }

  clone(): Form {
    const fields: Field[] = [];
    for (const field of this.fields) {
      fields.push(field.clone());
    }
    const form = new Form(this.category, this.description);
    form.fields = fields;
    return form;
  }
}

class CopyForm {
  constructor(readonly formRepository: FormRepository) {}

  async execute(input: Input): Promise<void> {
    const form = await this.formRepository.get(input.fromFormId);
    const newForm = form.clone();
    newForm.category = input.category;
    newForm.description = input.description;
    await this.formRepository.save(newForm);
  }
}

A clonagem elimina acoplamento desnecessario e lida naturalmente com propriedades privadas.

Combinando com Abstract Factory

O padrao funciona bem com Abstract Factory para criar instancias padronizadas e facilmente replicaveis:

interface FormFactory {
  createLeadCaptureForm(category: string, description: string): Form;
}

class PrototypeFormFactory implements FormFactory {
  createLeadCaptureForm(category: string, description: string): Form {
    const leadCaptureForm = new Form(category, description);
    leadCaptureForm.addField("name", "text");
    leadCaptureForm.addField("email", "text");
    return leadCaptureForm.clone();
  }
}

const formFactory = new PrototypeFormFactory();
const newForm = formFactory.createLeadCaptureForm("Marketing", "Lead Capture");

Usando com o Padrao Singleton

Alternativamente, combine Prototype com Singleton para registro dinamico de objetos:

class PrototypeManager {
  private prototypes: Record<string, Prototype> = {};
  static instance: PrototypeManager;

  add(name: string, prototype: Prototype) {
    this.prototypes[name] = prototype;
  }

  get(name: string) {
    return this.prototypes[name];
  }

  static getInstance() {
    if (!PrototypeManager.instance) {
      PrototypeManager.instance = new PrototypeManager();
    }
    return PrototypeManager.instance;
  }
}

const leadCaptureForm = new Form("Marketing", "Lead Capture");
leadCaptureForm.addField("name", "text");
leadCaptureForm.addField("email", "text");
PrototypeManager.getInstance().add("leadCaptureForm", leadCaptureForm);
const newForm = PrototypeManager.getInstance().get("leadCaptureForm").clone();

Heranca Baseada em Prototype

Linguagens como Self (derivada do Smalltalk) e JavaScript usam heranca baseada em prototype. Em JavaScript especificamente, propriedades não são verdadeiramente copiadas; em vez disso, superclasses são instancias compartilhadas que afetam todos os objetos que herdam -- um comportamento mais próximo do padrao Flyweight.

O objetivo é reusar objetos existentes seja por meio de copia (Self) ou referencias (JavaScript).

Estendendo Objetos Built-in

Você pode adicionar metodos a classes built-in:

Date.prototype.getFormattedDate = function () {
  const months = [
    "Janeiro",
    "Fevereiro",
    "Marco",
    "Abril",
    "Maio",
    "Junho",
    "Julho",
    "Agosto",
    "Setembro",
    "Outubro",
    "Novembro",
    "Dezembro",
  ];

  const day = this.getDate();
  const month = months[this.getMonth()];
  const year = this.getFullYear();

  return `${day} de ${month} de ${year}`;
};

Cuidado: Essas modificacoes são compartilhadas entre todas as instancias, criando fragilidade já que sobrescrever comportamento afeta todos os usuários.

Conclusão

O padrao Prototype e fundamental no design orientado a objetos, principalmente quando lidamos com objetos complexos e que precisam de alguma forma ser copiados de forma simples e desacoplada.

Continue aprendendo

Já conhece nossas formações?

Os desenvolvedores que vão se manter no futuro são os que dominam a Inteligência Artificial e são arquitetos de software. Aprenda como ser esse tipo de desenvolvedor.

Cookies e privacidade

Utilizamos cookies para melhorar sua experiência, analisar o tráfego do site e personalizar conteúdo. Você pode aceitar todos, rejeitar ou personalizar suas preferências.