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/
