Este texto não se trata de uma introdução a Orientação a Objetos, para isto, este artigo da MDN serve melhor.
Tive o prazer de palestrar sobre os paradigmas do JavaScript no BrazilJS deste ano, este texto é um complemento com alguns insights sobre Orientação a Objetos e em especial: construtores.
Inspiração
Costumo sempre ficar de olhos abertos para sugar ao máximo o que diferentes linguagens e suas comunidades tem a oferecer.
Aprendi Ruby há alguns anos atrás. Na época, o que mais me chamou atenção era que quase tudo pode se comportar como um objeto, assim como no JavaScript. Fique tranquilo, minha intenção aqui não é fazer com que você aprenda Ruby, só usarei a linguagem por alguns parágrafos para defender um ponto.
Nossa inspiração será uma versão simplificada da principal classe responsável pelos models no Ruby on Rails.
Considerando um model User
, posso executar User.new(name: 'Jean')
para instanciar um objeto. Neste caso, o método initialize
acima é chamado e name
é armazenado no objeto que acabei de criar.
O ponto chave: nenhum efeito colateral é ou deve ser desencadeado com a simples instanciação de um objeto. Esta execução, por exemplo, não salva este usuário no banco de dados ou o persiste de qualquer outra maneira que não seja como atributo do objeto recem criado.
Adicionalmente, nossa classe possui o método create
. Este método pode ser chamado ao invés do new
, que é o construtor padrão.
class ActiveRecord::Base
def self.create(attributes)
object = new(attributes)
object.save
object
end
end
Como você deve suspeitar, User.create(name: 'Jean')
instancia um objeto e o salva no banco de dados. Sim, agora temos efeito colateral, mas isto é claro pois tenho um método específico que possui este comportamento documentado.
Construtores ideais
O construtor ideal é aquele que não adiciona listeners de eventos ou elementos no DOM e muito menos dispara um alert
. E é óbvio que isto não é tão simples e a maioria dos códigos não seguem esta regra.
Modelo
Vamos portar nossa inspiração para JavaScript na forma de uma das aplicações mais comuns (e detestadas) de vermos em websites: um carrossel.
function Carousel(container) {
this.container = $(container);
}
Carousel.create = function (container) {
var instance = new this(container);
instance.init();
return instance;
};
Carousel.prototype.init = function () {
this.addEventListeners();
this.startAutomaticTransition();
};
Note, a única função do construtor é armazenar o container utilizado como base para o carrosel. O método de instância init
é que irá disparar o comportamento. Um exemplo de uso.
Temos outra forma de uso com o mesmo resultado. Repare que desta vez não usamos o operador new
.
Lembre-se sempre de tirar o máximo proveito da linguagem e definir seus métodos no prototype
. Desta maneira, uma única função será criada e compartilhada por todas as instâncias do seu construtor.
Vantagens
A principal vantagem é poder instanciar um objeto sem precisar se preocupar com efeitos colaterais, este é o ganho.
Sem dúvidas, herança em navegadores modernos deve ser apoiada em Object.create. Mas para uma técnica compatível com todos os navegadores, o construtor a ser herdado deve ser instanciado no prototype do sub construtor. A maneira mais comum de se fazer isto é criando um construtor temporário para que o construtor herdado não seja executado. Com construtores sem efeito colateral, esta etapa não é necessária.
function CarouselWithLasers(container) {
Carousel.call(this, container);
}
CarouselWithLasers.prototype = new Carousel();
CarouselWithLasers.prototype.constructor = CarouselWithLasers;
Viabilizar os testes é outra grande vantagem em não ter comportamento definido no construtor. Desta forma, fica possível aplicar stubs no método init
para poder testar e ser feliz.
Na biblioteca Backbone, que é um dos cases mais fantásticos de herança em JavaScript que conheço, todos os construtores quando não extendidos podem ser instanciados sem efeitos colaterais. Isto vale para new Backbone.Model()
, new Backbone.View()
, new Backbone.History()
. Pode experimentar, faça estas chamadas no console do seu navegador quando estiver acessando o endereço http://backbonejs.org
Um último detalhe é que, para as views do Backbone, a documentação incentiva o uso da propriedade events
, que associa listeners na instanciação do objeto. Tem também o método this.listenTo
geralmente usado no initialize
, outro que é chamado na instanciação. Não digo que não devam ser utilizados, apenas aconselho que fique atento.
Edição 1: Chamar de create
o método do construtor evita confusões e deixa mais clara qual a sua real função.
Edição 2: Incentivo ao uso do prototype
como essência da definição de um bom construtor.