Design Pattern Observer em Javascript

E aí galerinha do mal, é nóis again.

É engraçado que sempre ao se começar um post eu fico horas só pensando na saudação rsrss. É difícil inovar.. mas ainda que seja repetitivo é melhor que aquele “Bom dia” que alguns colocam né ? Caray, quem garante que a criatura que vai ler, estará lendo de manhã ? =/

Deixando de lado as viadagens, vamos ao que nos interessa de verdade que é Javascript na veia irmão.

Saber sobre os Design Patterns e onde aplicar é o que diferencia os programadores homens dos programadores meninos. Às vezes não é tão trivial identificar qual design pattern usar para um determinado problema e não é possível saber todos decor.

E é por isso que estou postando bastante sobre esse assunto, para termos uma referência na nossa linguagem sobre isso.

Então, o que é um Observer e quando usar ?

Tá de pé? Então senta.

O padrão Observer é um padrão do tipo comportamental, no qual um objeto chamado “Subject” contém uma lista de dependentes, chamados Observers. Os Subjects então notificam para esta lista de Observers automaticamente quando algum estado modifica, geralmente chamando um de seus métodos.

É geralmente usado para implementar sistemas distribuidos com delegação de eventos.

Fonte: Wikipédia

Este padrão usa alguns conceitos de outros design patterns, como Interfaces e Visitor. Lembram que eu falei pra estudar bastante sobre Interfaces pois todos os outros design patterns utilizam este conceito ? Este padrão utiliza interfaces e utiliza também uma idéia do padrão Visitor que se chama inversão de métodos. O Visitor não é pré-requisito para entender o Observer, mas é interessante saber que muitos design patterns utilizam conceitos de outros para se compor.

Basicamente é isso, quando temos um objeto que ao ser alterado uma propriedade dele, outros objetos precisam saber que este objeto foi alterado e precisam invocar um método deles assim que isso acontecer.

Em Javascript o Observer pode ser amplamente usado, porque lidamos com eventos mais do que qualquer coisa em Javascript.

É bastante necessário por exemplo, quando temos uma tela que é dinâmica a nível de Js, com paginação e tal. Quando por exemplo vamos para a próxima página da paginação, criamos novos elementos de acordo com novos dados e estes elementos perdem todos os eventos que os outros tinham na página anterior. Temos que ficar atualizando os eventos dos objetos quando nossa tela é alterada. O Pattern Observer pra isso é uma mão na roda.

Mas eu vou dar um exemplo bem simples, talvez não muito usual, mas para ser facilmente fixado e absorvido por vocês meus caros leitores.

Primeiro, nosso diagrama de classes do Pattern.
Diagrama de Classes - Pattern Observer

Agora nosso problema.

Temos dois objetos, Tela e Ponto, onde o objeto Tela será responsável por mostrar o objeto Ponto na tela. Porém o objeto Ponto tem um estado, que é a posição dele na tela, ele tem um atributo x e um atributo y que são suas coordenadas.
Sempre que eu alterar uma das suas coordenadas ( x ou y ) o objeto Tela precisa saber que isso aconteceu e atualizar sua posição na tela.

Portanto, vamos resolver esse problema usando o Pattern Observer.
Como o objeto Tela precisará observar o objeto Ponto, então será este nosso Observer. Então Ponto será nosso Subject.

Usando nosso “Javiani way” de construir Classes, o padrão Observer é construido da seguinte maneira:

var Interface = {
	
	Observer : function(){ 
		this.update = function(o){ 
			throw new Error('Implemente Observer.update()') 
		}
	}, 
	
	Subject : function(){
		
		var observers = {}
		
		this.add_observer = function(o){ observers[o] = o }					
		this.rem_observer = function(o){ delete observers[o] }
		this.notify = function(){
			for(var x in observers)
				observers[x].update(this)
		}				
	}
}

Lembrando que Observer e Subject são interfaces, não classes. Portanto, teremos duas classes, Tela e Ponto, na qual cada uma delas IMPLEMENTARÁ um tipo de interface.

A interface acima Observer, terá um método obrigatório, que será o update, que fará alguma coisa com o objeto passado como argumento para o método. Este método será chamado pelo objeto Subject, notificando o Observer que um estado do Subject foi alterado.

A interface Subject, terá uma lista contendo todos os observers que um objeto Subject terá. E também terá 3 métodos. Um para adicionar um observer à lista, outro pra remover um observer da lista, e outro para passear sobre a lista e avisar à todos os seus Observers registrados que alguma coisa foi alterada, chamando o método update dos Observers passando o objeto que foi alterado. No nosso caso este objeto seria um objeto do tipo Ponto.

Vamos as classes Tela e Ponto então.

Classe ponto com um setter/getter para os pontos x e y. Ela implementará a interface Subject e notificará aos seus Observers que este ponto foi alterado no momento em que for setado um valor novo para x ou para y:

var Ponto = {
	Class : function(){
		Interface.Subject.apply( this )
		
		var x = 0;
		var y = 0;
		
		this.x = function(i){
			if(i){
				x = i
				this.notify()
			}
			else return x
		}
		
		this.y = function(i){
			if(i){
				y = i
				this.notify()
			}
			else return y
		}
	},
	New : function(){ return new this.Class() }
}

Agora a Classe Tela, que implementará Interface.Observer, terá um método update obrigatório que será chamado pelo Subject, mandando o subject como argumento.

var Tela = {
	Class : function(object){
		Interface.Observer.apply(this)
	
		var html = object
		
		this.desenha = function(o){
			this.desenha[o.constructor].apply(this, arguments)
		}
		
		this.desenha[ Ponto.Class ] = function(o){
			html.style.left = o.x() + 'px'
			html.style.top =  o.y() + 'px'
		}
		
		this.update = function(o){ this.desenha(o) }				
	},
	New : function(object){ return new this.Class(object) }
}

Chamando um pouco a atenção à forma como montei a Tela, primeiro implementei a interface Observer, o construtor da classe Tela recebe como parâmetro um objeto html e o guarda numa variável privada. Esta classe tem um método sobrecarregado this.desenha. Por que imagino que este objeto Tela não desenhará apenas um ponto mas outros elementos futuramente. ( Se não conhece esta forma de overload, veja o artigo anterior à este )

Então o método sobrecarregado desenha que recebe um ponto, apenas definirá a posição em pixel, no atributo Css left, e top considerando que este objeto html esteja em posição absoluta ou relativa.

O método update será chamado pelo objeto Ponto assim que se alterar qualquer uma das suas coordenadas, isso será feito pelo método notify.

Pronto, agora vamos instanciar os objetos e fazer o padrão Observer funcionar:

window.onload = function(){
	
	var dot = document.getElementById('dot')
	
	var tela  = Tela.New(dot) // Ou new Tela.Class(dot)
	
	var ponto = Ponto.New() // Ou new Ponto.Class				
		ponto.add_observer(tela)					
		ponto.x(490)
		ponto.y(80)
	
}

Aí está, pegamos um elemento html na tela, o passamos como argumento para o construtor de Tela e instanciamos o objeto tela.

Logo depois disso instanciamos o objeto ponto, adicionamos um observer à ele, que será o objeto tela.
Assim que setarmos um novo valor para x ou para y, o método notify em ponto será chamado, este fará uma chamada do método update de todos os observers registrados passando o objeto ponto como argumento.

O objeto tela ao ser notificado que houve uma alteração em ponto, desenhará na tela a nova posição do ponto. =)

Sacaram ?

Deixei disponível um exemplo deste código, abram-o no firefox de preferência, e setem um novo valor para o ponto, para x ou para y, ou para os dois no console. Deixei públicos os objetos tela e ponto.

Ahh, para não perder o costume de Cientista, aqui vai uma funçãozinha pra fazer o ponto animado =)

var t = 0.0
var fx = function(x, r, t){ return (x + r*Math.cos(t/2)) }
var fy = function(x, r, t){ return (x + r*Math.sin(t/2)) }

setInterval(function(){

        if( t == 12 ) t = 0
    ponto.x( fx(screen.width/2, 40, t) )
    ponto.y( fy(screen.height/2 - 180, 40, t) )
    t+=.5

}, 100)

Só colar no firebug e executar =D.

Um grande abraço galera, valeu!

Anúncios