Desenvolvimento

22 fev, 2017

Trabalhando com Função como Objeto

Publicidade

Em programação, a noção fundamental de um objeto é: a união de dados e comportamento. Isso fornece um contexto de dados comum ao escrever um conjunto de funções relacionadas. Também fornece uma interface à manipulação de dados que permite que o objeto controle o acesso a esses dados, facilitando o suporte a dados derivados e evitando modificações inválidas de dados. Muitas linguagens fornecem sintaxe explícita para definir classes, que atuam como definições para objetos. Mas se você tiver uma linguagem com funções e closures de primeira classe, você pode usar essas construções para criar objetos usando o padrão Função Como Objeto (originalmente descrito por Eugene Wallingford).

Aqui está um exemplo de um objeto person simplista, feito usando o estilo função como objeto em JavaScript.[1]

function createPerson(name) {
  let birthday;
  return {
    name: () => name,
    setName: (aString) => name = aString,
    birthday: () => birthday,
    setBirthday: (aLocalDate) => birthday = aLocalDate,
    age: age,
    canTrust: canTrust,
  };
  function age() {
    return birthday.until(clock.today(), ChronoUnit.YEARS);
  }
  function canTrust() {
    return age() <= 30;
  }
}

A forma externa de uma função como objeto é uma função, que é chamada como uma função construtora. O resultado da chamada é, em essência, um hashmap de funções [2] que atua como seletor de método. Este mapa capta o estado de todas as variáveis na função em uma closure, permitindo que os dados persistam além de uma única invocação de função. Este resultado hashmap pode ser tratado como um objeto clássico.

const kent = createPerson("kent");
kent.setBirthday(LocalDate.parse("1961-03-31"));
const youngEnoughToTrust = kent.canTrust();

Observando a função como objeto de um ponto de vista OO clássico:

  • os campos do objeto são representados pelos parâmetros para a função de construtor (name) juntamente com as variáveis locais (birthday).
  • os métodos do objeto são as funções aninhadas dentro da função do construtor. Como métodos de objeto eles podem chamar livremente uns aos outros e manipular os dados nessas variáveis com escopo local (campos)
  • nada fora da função do construtor pode acessar as variáveis, preservando o encapsulamento de dados
  • os métodos públicos do objeto são aquelas funções que estão presentes no resultado hashmap
  • todas as funções aninhadas dentro da função do construtor mas não presentes no resultado hashmap são métodos particulares
  • os nomes dos métodos públicos são as chaves do resultado hashmap, não os nomes das funções dentro da função de construtor. Eu prefiro manter as teclas e nomes de função as mesmas para evitar confusão (embora possa ser útil para fazer alias de funções, se necessário). [3]

Uma implementação alternativa comum deste padrão é retornar uma função como seletor de método em vez do hashmap que é o seletor de método natural no JavaScript. Para usar uma função como seletor de método, eu retornaria uma função cujo primeiro argumento fosse o nome do método a ser invocado. O corpo da função então liga esse valor (veja Wallingford para mais sobre isso).

A abordagem função como objeto tem estado por aí por um longo tempo, eu vi isso descrito em lisp muitas vezes e tem sido amplamente utilizado em JavaScript (até ES6, JavaScript tinha uma noção muito limitada de classes). É frequentemente usada como um argumento de que uma sintaxe específica para classes não é necessária, que é o equivalente a object-aficionados argumentando que você não precisa de funções de primeira classe quando você pode escrever uma classe com um único método “de chamada”. Como consequência, muitas pessoas no mundo do JavaScript argumentam contra o uso da sintaxe da classe ES6. Pessoalmente, eu gosto de ter tanto as funções de primeira classe quanto as classes de primeira classe e prefiro a sintaxe de classe ES6.

Leitura adicional

Eugene Wallingford cunhou o nome “Function as Object” em sua linguagem padrão “Envoy”, de 1999. Seu artigo vale a pena ser lido para obter mais detalhes sobre isso, incluindo o uso de uma função como o seletor de métodos e delegação para apoiar alguma noção de herança. Os exemplos no artigo usam Scheme.

Agradecimentos

Chris Ford, Fred George, James Shore, Kevin Yeung, Lucas Lego, Matteo Vaccari, Rob Miles e Eugene Wallingford comentaram os rascunhos deste post

Notas

[1] Para manipulação de data eu estou usando js-joda, uma porta da biblioteca de Joda-Time que limpou a confusão terrível que era data de Java e manipulação de tempo. Estou feliz que joda-js está repetindo o serviço de trazer a sanidade para a data e tempo de manipulação.

[2] Em terminologia JavaScript isto é chamado de objeto, embora seja um objeto JavaScript, não o objeto clássico que estamos tentando criar. Vou, portanto, referir-me a ele como um hashmap, para tentar reduzir a confusão.

[3] No ES6 eu posso usar nomes abreviados de propriedade para remover a duplicação, substituindo “age: age”, por “age”.

 

***

Martin Fowler faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link:  https://martinfowler.com/bliki/FunctionAsObject.html