RSS Feed

Classes e Módulos.

25/10/2011 by urieljuliatti

Fala rapazeada, tudo na ‘sussabilidade’?!

Pois é, vamos aqui seguindo mais um post para contribuir para essa longa e maravilhosa jornada que estou percorrendo com o ruby.

Resolvi ir direto para um tema que estou tendo bastante curiosidade no momento, que é sobre Classes e Módulos com ruby, especialmente os módulos.

Começarei com as Classes, cuja abordagem é vasta, podendo ser dividida em várias etapas. É bem capaz que eu resuma muita coisa, dando ênfase apenas as partes relevantes, mas sempre deixando algumas referências para acrescentar e talvez divida esse post em séries de posts, para facilitar a divisão dos tópicos.

Antes de tudo.

Como muitos de nós sabemos, Ruby  é uma linguagem puramente Orientada a Objetos, cada valor é (ou ao menos se comporta) como um objeto. Todo objeto é uma instância de uma classe. E uma classe define uma série de métodos que respondem a um objeto. Por convenção de arquitetura de software, uma classe pode extender ou ser uma subclasse de outras classes e herdar ou sobrescrever métodos de suas superclasses. Em Ruby, as classes também podem ter métodos herdados de módulos.

Os objetos são estritamente encapsulados: Os estados só podem ser acessados por seus métodos já definidos. As variáveis de instância manipuladas por esses métodos não podem ser acessadas diretamente por fora do objeto. É possível gerar getters e setters, acessando diretamente o estado do objeto. Esses acessores são conhecidos como atributos são diferentes das variáveis de instância. A visibilidade dos métodos de uma classe podem ser definidas como “public”, “private” ou “protected” que afetam drásticamente na forma que eles são chamados.

Ao contrário do que se pensa sobre as restrições geradas pelo encapsulamento de um estado do objeto, as classes em Ruby são bastante abertas. Qualquer programa em ruby pode adicionar métodos para classes já existentes e até mesmo métodos singleton para objetos específicos. É um conceito bem interessante, pois ao fazer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test
 
   def user(nome)
 
       nome ||= "João Padrão"
 
   end
 
end
 
# abrindo a mesma classe e editando-a novamente
 
def Test
 
    def user(nome, cpf)
 
        nome ||= "Pedro Padrão"
 
        cpf ||= "106.778.900-90"
 
    end
 
end
class Test

   def user(nome)

       nome ||= "João Padrão"

   end

end

# abrindo a mesma classe e editando-a novamente

def Test

    def user(nome, cpf)

        nome ||= "Pedro Padrão"

        cpf ||= "106.778.900-90"

    end

end

O que vamos fazer inicialmente é definir uma classe e seus métodos. Muita coisa aqui vai ser retomada posteriormente, com uma abordagem mais avançada :)

Definindo uma classe.

Baby Steps, é um conceito que meu amigo @m3nd3s sempre aconselha. Seguindo esse conselho, vamos dar início a esse tópico definindo uma classe bem boba, mas que aborda um aprendizado conciso.

O que precisamos para criar uma classe? Definí-la. É tudo muito simples:

1
2
3
class Ponto
 
end
class Ponto

end

Com a palavra chave “class” definimos uma classe, seguindo pelo end, que fecha seu escopo. Não sei vocês observaram, a classe foi nomeada com a inicial maiúscula, é uma regra do Ruby, obrigatória por sinal. O nome da constante e o nome da classe são os mesmos, você verá isso posteriormente com mais detalhes.

Instanciando.

Mesmo se não colocarmos nada na classe Ponto, podemos instanciá-la:

1
2
ruby-1.9.2-p290 :005 > p = Ponto.new
=> #<Ponto:0x00000106b99c70>
ruby-1.9.2-p290 :005 > p = Ponto.new
=> #<Ponto:0x00000106b99c70>

A constante Ponto mantêm em um objeto o que representa a nossa classe. Todos os objetos possuem um método chamado new que é responsável por criar uma nova instância. Não há muita coisa interessante a fazer com esse objeto que criamos, apenas verificar algo do tipo:

1
2
3
4
5
ruby-1.9.2-p290 :006 > p.class
=> Ponto
 
ruby-1.9.2-p290 :008 > p.is_a?Ponto
=> true
ruby-1.9.2-p290 :006 > p.class
=> Ponto

ruby-1.9.2-p290 :008 > p.is_a?Ponto
=> true

Inicializando.

Se você veio de outras linguagens orientadas a objeto, certamente deve conhecer o termo “construtor”, é exatamente isso que
o initialize faz.

1
2
3
4
5
6
7
class Ponto
 
   def initialize(x,y)
       @x, @y = x,y
   end
 
end
class Ponto

   def initialize(x,y)
       @x, @y = x,y
   end

end

Com esse método definido, podemos criar outros ponteiros e ver o seu retorno.

1
2
3
4
5
ruby-1.9.2-p290 :008 > p = Ponto.new(1,10)
 => #<Ponto:0x00000105dd6fc0 @x=1, @y=10>
 
ruby-1.9.2-p290 :009 > p
 => #<Ponto:0x00000105dd6fc0 @x=1, @y=10>
ruby-1.9.2-p290 :008 > p = Ponto.new(1,10)
 => #<Ponto:0x00000105dd6fc0 @x=1, @y=10>

ruby-1.9.2-p290 :009 > p
 => #<Ponto:0x00000105dd6fc0 @x=1, @y=10>

Repare bem nas variáveis de instância x e y, que ja foram inicializadas para o objeto corrente com o valor de 1 e 10 para x e y, respectivamente.
Acessores e Atributos

Como falei anteriormente, a classe Ponto utiliza duas variáveis de instância, contudo, o valor dessas variáveis estão acessiveis apenas para outros métodos de instância. Isto significa se quisermos que outros objetos da classe queiram utilizar métodos para acessar o valor de X e Y, teremos que fornecer métodos acessores que retornariam os valores dessas variáveis.

Ao pé da letra, para quem vem de outras linguagens Orientadas a Objeto, faríamos assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ponto
 
   def initialize(x,y)
       @x, @y = x,y
   end
 
   def x
      @x
   end
 
   def y
      @y
   end
 
end
class Ponto

   def initialize(x,y)
       @x, @y = x,y
   end

   def x
      @x
   end

   def y
      @y
   end

end

Uma vez definindo esses métodos, poderíamos fazer isso:

1
2
3
4
5
ruby-1.9.2-p290 :023 > p = Ponto.new(1,2)
 => #<Ponto:0x00000105db3700 @x=1, @y=2>
 
ruby-1.9.2-p290 :024 > q = Ponto.new(p.x*2, p.y*3)
 => #<Ponto:0x00000105dab870 @x=2, @y=6>
ruby-1.9.2-p290 :023 > p = Ponto.new(1,2)
 => #<Ponto:0x00000105db3700 @x=1, @y=2>

ruby-1.9.2-p290 :024 > q = Ponto.new(p.x*2, p.y*3)
 => #<Ponto:0x00000105dab870 @x=2, @y=6>

Agora, para fazermos um setter, faríamos algo do tipo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Ponto
 
   def initialize(x,y)
       @x, @y = x,y
   end
 
   def x
      @x
   end
 
   def y
      @y
   end
 
   def x=(value)
       @x = value
   end
 
   def y=(value)
       @y = value
   end
 
end
class Ponto

   def initialize(x,y)
       @x, @y = x,y
   end

   def x
      @x
   end

   def y
      @y
   end

   def x=(value)
       @x = value
   end

   def y=(value)
       @y = value
   end

end

Dessa forma, modificaríamos o valor da variável de instância do objeto corrente da seguinte jeito:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ruby-1.9.2-p290 :045 > p = Ponto.new(1,1)
 => #<Ponto:0x00000105d790f0 @x=1, @y=1>
 
ruby-1.9.2-p290 :046 > p.x = 0
 => 0
 
ruby-1.9.2-p290 :047 > p
 => #<Ponto:0x00000105d790f0 @x=0, @y=1>
 
ruby-1.9.2-p290 :048 > p.y = 50
 => 50
 
ruby-1.9.2-p290 :049 > p
 => #<Ponto:0x00000105d790f0 @x=0, @y=50>
ruby-1.9.2-p290 :045 > p = Ponto.new(1,1)
 => #<Ponto:0x00000105d790f0 @x=1, @y=1>

ruby-1.9.2-p290 :046 > p.x = 0
 => 0

ruby-1.9.2-p290 :047 > p
 => #<Ponto:0x00000105d790f0 @x=0, @y=1>

ruby-1.9.2-p290 :048 > p.y = 50
 => 50

ruby-1.9.2-p290 :049 > p
 => #<Ponto:0x00000105d790f0 @x=0, @y=50>

Porém, com ruby podemos simplificar a nossa vida, muitas vezes com apenas uma linha. Nesse exemplo, seguiremos o legado rubyway e faremos os getters e setters em apenas duas linhas:

1
2
3
4
5
class Ponto
 
   attr_accessor : x, :y
 
end
class Ponto

   attr_accessor : x, :y

end

Faça a mesma coisa agora no terminal para você ver o retorno:

1
2
3
4
5
6
7
8
ruby-1.9.2-p290 :065 > p = Ponto.new(10,10)
 => #<Ponto:0x00000105d1a528 @x=10, @y=10>
 
ruby-1.9.2-p290 :066 > p.x = 20
 => 20
 
ruby-1.9.2-p290 :067 > p
 => #<Ponto:0x00000105d1a528 @x=20, @y=10>
ruby-1.9.2-p290 :065 > p = Ponto.new(10,10)
 => #<Ponto:0x00000105d1a528 @x=10, @y=10>

ruby-1.9.2-p290 :066 > p.x = 20
 => 20

ruby-1.9.2-p290 :067 > p
 => #<Ponto:0x00000105d1a528 @x=20, @y=10>

Existem os métodos attr_writer (setter), attr_reader (getter) e o que utilizamos, o attr_accessor (getter e setter), eles são responsáveis por criarem métodos de instância para nós :)

Esse é um exemplo clássico de metaprogramação, e a habilidade de fazer isso é um dos maiores recursos que o ruby oferece.

Métodos de Classe.

Para quem já conhece a Orientação a Objetos, sabe que um método de classe pode ser acessado apenas pela própria classe seja lá onde ele for chamado. Isto é, no exemplo que estamos seguindo, o método de classe que vamos criar agora não é um método de instância que seria chamado em um objeto Ponto.

A chamada para o método de classe seria algo como:

1
total = Ponto.soma(p1, p2,p3)
total = Ponto.soma(p1, p2,p3)
1
2
3
4
5
6
7
8
9
10
11
class Ponto
 
   attr_reader : x, :y
 
   def self.soma(*pontos)
       x = y = 0
       pontos.each {|p| x += p.x; y += p.y}
       Ponto.new(x,y)
   end
 
end
class Ponto

   attr_reader : x, :y

   def self.soma(*pontos)
       x = y = 0
       pontos.each {|p| x += p.x; y += p.y}
       Ponto.new(x,y)
   end

end

Dessa forma, estaremos acessando a soma de todos os x e y de cada ponto através de um método de classe :)

Constantes.

Várias classes podem ser beneficiadas por uma associação e definição de algumas constantes, eis um exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Ponto
 
def initialize(x,y)
 
    @x, @y = x,y
 
end
 
ORIGEM = Ponto.new(0,0)
 
COORD_X = Ponto.new(1,0)
 
COORD_Y = Ponto.new(0,1)
 
[...]
 
end
class Ponto

def initialize(x,y)

    @x, @y = x,y

end

ORIGEM = Ponto.new(0,0)

COORD_X = Ponto.new(1,0)

COORD_Y = Ponto.new(0,1)

[...]

end

Uma vez dentro da classe, as constantes podem ser acessadas pelos nomes, diretamente. Caso estejam fora, para serem acessadas, devem estar acompanhada no nome da classe, como no exemplo:

1
Ponto::COORD_X + PONTO::CORD_Y
Ponto::COORD_X + PONTO::CORD_Y

Você pode inicializar até mesmo fora da classe:

1
2
ruby-1.9.2-p136 :206 > Ponto::COORD_X = Ponto.new(2,0)
 => #<Ponto:0x00000103bb2728 @x=2, @y=0>
ruby-1.9.2-p136 :206 > Ponto::COORD_X = Ponto.new(2,0)
 => #<Ponto:0x00000103bb2728 @x=2, @y=0>

O tópico é enorme, portanto, vou extendê-lo em outros posts. Para o próximo post trarei tópicos que abordam Variáveis de Classe, Variáveis de Instância e Visibilidade.

Referências:

http://www.ruby-doc.org/docs/ProgrammingRuby/html/classes.html

http://www.devarticles.com/c/a/Ruby-on-Rails/Ruby-Classes-and-Objects/

http://blog.bluesoft.com.br/ruby-classes-e-objetos/


No Comments »

No comments yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">