Se você é um desenvolvedor Ruby iniciante ou experiente, é importante estar familiarizado com os diferentes Design Patterns desta e outras linguagens de programação.
Este artigo revisita alguns conceitos sobre design patterns — especificamente, o pattern Observer — e então apresenta um exemplo feito com a biblioteca padrão de Ruby.
O livro Design Patterns: Elements of Reusable Object-Oriented Software, da Gangue dos Quatro (Gang of Four - GoF) descreve 23 padrões de projeto. Nesta coleção, está incluso o pattern Observer, também conhecido como publish/subscribe.
Se você é desenvolvedor, já deve conhecer o livro — ou pelo menos já ouviu falar e sabe do que se trata.
Mesmo assim, é bom relembrar e vale a pena repetir que cada linguagem traz construções idiomáticas que facilitam ou dificultam a utilização de algum pattern.
A natureza estruturada (não orientada a objetos) da linguagem C, torna um pouco mais trabalhoso usar esses patterns, enquanto na linguagem Ruby temos construções, como os blocks, que tornam o pattern Visitor um cidadão de primeira classe, por exemplo.
Neste ensaio vamos estudar o pattern Observer — como aplicar em Ruby usando a biblioteca padrão que já vem com o interpretador MRI.
Imagine que você tem um objeto e este possui um estado interno. Imagine também que, quando esse estado mudar, outros objetos devem ser notificados e reagir de alguma forma a esta mudança.
Seria muito conveniente estabelecer essas relações sem criar muito acoplamento. O ideal é ter esses acontecimentos de forma automática: basta declarar que existe um relacionamento e tudo acontece num instante.
Existem muitas implementações diferentes desse pattern, mas a biblioteca padrão do Ruby tem um arquivo ‘observer.rb’ que faz tudo que precisamos. É essa implementação que estudaremos neste artigo.
Essa interação tem dois tipos de participantes. Um objeto dotado de algum atributo interessante de ser observado e um objeto observador, que reage à mudança de estado do objeto observado.
Primeiro, vamos criar duas classes: uma chamada Subject e outra chamada Observer.
Para que isso aconteça, é preciso incluir a biblioteca 'observer' no arquivo onde é definida a classe do objeto observado, como no snippet a seguir:
In [1]: |
Vamos criar uma classe chamada Subject e, dentro da definição da classe do objeto observado, vamos incluir um módulo mixin e definir métodos acessores de uma variável state, como no exemplo a seguir:
In [2]: |
Ao incluir o mixin Observable, a classe Subject recebe a definição de alguns métodos novos. Os três métodos mais importantes são:
Os dois últimos são mais internos; o add_observer pode ser chamado externamente. Faz sentido que o add_observer seja apenas chamado por outros objetos, pois desta forma o objeto observado sequer precisa ter conhecimento de quais são os objetos que vão reagir à mudança de estado.
Precisamos ter uma classe Observer. O objeto observador precisa implementar um método que será chamado sempre que ocorrer uma mudança de estado no objeto observado. Embora ele possa ter qualquer nome, a convenção é que esse método se chame update.
|
Agora vamos instanciar essas classes e fazê-las conversarem entre si:
In [4]: |
A utilização do padrão Observer traz algumas consequências. Segundo os autores, ao utilizar este pattern podemos variar os objetos observado e observador de forma independente.
O relacionamento entre estes objetos é um acoplamento fraco: o objeto observado possui internamente uma lista dos observadores, mas não tem os detalhes da implementação deles. Tampouco precisa saber o que eles fazem com a informação notificada.
É possível ter muitos observers para um mesmo subject, cada um reagindo de um jeito diferente à mudança de estado.
No livro, o autor Erich Gamma alerta que, se não tomarmos cuidado e criarmos uma cadeia de objetos que se auto-notificam sobre suas mudanças, eles podem consumir recursos valiosos e até mesmo ter um loop infinito de auto-atualização. Portanto, tome cuidado para que isso não ocorra na sua implementação.
Há muitas outras coisas para falar sobre esse pattern, principalmente sobre essa implementação da biblioteca de Ruby. Ela não é a única implementação. Tem uns probleminhas, mas nada que prejudique a usabilidade ou a implementação. Ela já está na distribuição e é gratuita.