Cap. 4 - Acessores, Atributos, Variáveis de Classe

Anterior Índice Próximo

Acessores, Atributos e Variáveis de Classes…

Agora, voltando para o pequeno jogo de estratégia visto anteriormente… Tem um probleminha nele que incomoda bastante pessoas que não gostam de código repetitivo, que é todos os acessores get e set. É de deixar qualquer um louco.. não é mesmo? wink

Vamos ver o que se pode fazer para remediar isso.

Métodos Acessores

Em vez de acessar o valor da variável de instância @descricao com dois métodos diferentes, get_descricao e set_descricao, como a seguir:

  puts( t1.get_descricao )
  t1.set_descricao( "Alguma descrição")

… seria muito mais fácil recuperar e atribuir valores da mesma forma que você recupera e atribue valores para variáveis simples, como a seguir:

puts( t1.descricao )
t1.descricao = "Alguma descrição"

Para poder fazer isso, eu preciso modificar a definição da classe Tesouro. Uma forma seria reescrever os métodos acessores para @descricao como segue:

def descricao
  return @descricao
end

def descricao=( descricao )
  @descricao = descricao
end

Modificando o nosso programa descrito anteriormente, "estrategia.rb", utilizando esta nova técnica ficamos com:

acessores.rb


# Como criar objetos descendentes

class Coisa
      def initialize( nome, descricao )
        @nome         = nome
        @descricao  = descricao
      end
      
      def nome
          return @nome
      end
      
      def nome=( nome )
          @nome = nome
      end
      
      def descricao
          return @descricao
      end
      
      def descricao=( descricao )
          @descricao = descricao
      end
end      
    
    
class Tesouro < Coisa #Tesouro descende de Coisa
      def initialize( nome, descricao, valor )
          super( nome, descricao )
          @valor = valor
      end
      
      def valor
          return @valor
      end
      
      def valor=( valor )
          @valor = valor
      end
end
    
# Início do programa
  t1 = Tesouro.new("Espada de Arwen", "uma arma élfica.", 800)
  t2 = Treasure.new("Anduril", "espada do rei Aragorn", 550) 
  puts "Este é tesouro1: #{t1.inspect}"
  puts "Este é um tesouro2: #{t2.inspect}"
  puts "t1 nome=#{t1.nome}, descrição=#{t1.descricao}, valor=#{t1.valor}"
  t1.set_valor( 100 )
  t1.set_nome( 'Fighting Knives' )
  t1.set_descricao("facas voadoras de Legolas")
  puts "t1 (Agora) nome=#{t1.nome}, descricao=#{t1.descricao}, valor=#{t1.valor}"

Existem duas diferenças do código "acessores.rb" para o código "estrategia.rb". Primeiro, ambos os acessores são chamados descricao ao invés de get_descricao__e _set_descricao; segundo, o acessor _set anexa um sinal de igual ( = ) ao nome do método. Agora é possível atribuir uma nova string para @descricao assim:

t.descricao = "espada do rei Aragorn"

E você pode recuperar o valor assim:

puts( t.descricao )

Quando você escreve um acessor set desta forma, você deve colocar o caracter = diretamente após no nome do método, não meramente colocá-­lo em qualquer lugar entre o nome do método e seus argumentos. Então, isto é correto:
                def nome=( nome )

Mas isto é errado:

              def nome =( nome )

Readers e Writers de Atributos

De fato, existe uma forma mais simples e curta de obter o mesmo resultado descrito anteriormente. Tudo que você precisa fazer é usar dois métodos especiais, attr_reader e attr_writer, seguidos de um símbolo demonstrado abaixo:

attr_reader :descricao
attr_writer :descricao

Você pode adicionar este código dentro da definição da classe mas fora de quaisquer de seus métodos, desta forma:

class Coisa
   attr_reader :descricao
   attr_writer :descricao

   # Alguns método aqui...

end

Símbolos: No Ruby, um símbolo é um nome precedido pelo sinal de dois pontos (:). Symbol é definido na biblioteca de classes do Ruby para representar nomes dentro do interpretador Ruby. Símbolos têm alguns usos especiais. Por exemplo, quando você passa um ou mais símbolos como argumentos par a o método attr reader (pode não ser óbvio, mas attr_reader é, de fato, um método da classe Module), o Ruby cria uma variável de instância e um método accessor _get para retornar o valor da variável; ambos a variável de instância e o método acessor terão o mesmo nome que o símbolo especificado.

Chamando attr_reader com um símbolo têm o efeito de criar uma variável de instância com o mesmo nome do símbolo e um acessor get para aquela variável. Chamando attr_writer, semelhantemente cria uma variável de instância com um acessor set. Aqui, a variável seria chamada de @descricao. Variáveis de instância são consideradas como os 'atributos' de um objeto, o que explica porque os métodos attr_reader e attr_writer methods são assim chamados.

Melhorando ainda mais o nosso programa "estrategia.rb" podemos modificar o programa "acessores.rb", incorporando os novos conceitos aprendidos agora. Vamos chamar este novo programa de "acessores_attr.rb".

acessores_attr.rb


# Como criar objetos descendentes

class Coisa

      attr_writer :nome
      attr_reader :nome

      attr_accessor :descricao

      def initialize( nome, descricao )
        @nome         = nome
        @descricao  = descricao
      end
      
end      
    
    
class Tesouro < Coisa #Tesouro descende de Coisa
      attr_writer :valor

      def initialize( nome, descricao, valor )
          super( nome, descricao )
          @valor = valor
      end
      
      def valor
          return @valor.to_f
      end
      
end
    
# Início do programa
  t1 = Tesouro.new("Espada de Arwen", "uma arma élfica.", 800)
  t2 = Treasure.new("Anduril", "espada do rei Aragorn", 550) 
  puts "Este é tesouro1: #{t1.inspect}"
  puts "Este é um tesouro2: #{t2.inspect}"
  puts "t1 nome=#{t1.nome}, descrição=#{t1.descricao}, valor=#{t1.valor}"
  t1.set_valor( 100 )
  t1.set_nome( 'Fighting Knives' )
  t1.set_descricao("facas voadoras de Legolas")
  puts "t1 (Agora) nome=#{t1.nome}, descricao=#{t1.descricao}, valor=#{t1.valor}"

O programa acessores_attr.rb contém alguns exemplos de readers e writers de atributos em ação. Note que a classe Tesouro define a forma simplificada do acessores set (usando attr_writer mais um símbolo) para a variável @valor:

attr_writer :valor

Mas tem o acessor get na sua forma longa - um método codificado inteiramente - para a mesma variável:

def valor
       return @valor.to_f
end

A vantagem de escrever um método completo como esse é que dá a você a oportunidade de fazer algum processamento extra, ao invés de somente ler e escrever o valor de um atributo. Aqui o acessor get usa o método to_f da classe Fixnum para retornar o valor @valor sempre como float.

O atributo @nome não precisa de processamento especial, então pode-se utilizar os métodos attr_reader e __attr_writer para pegar e atribuir o valor da variável @nome.

Atributos ou Propriedades? Não seja confundido pela ter minologia. No Ruby, um "atributo" é equivalente ao que muitas outras linguagens de programação chamam de "propriedade" do objeto.

Quando você quer ler e escrever uma variável, o método attr_accessor fornece uma alternativa mais curta que usar ambos os métodos attr_reader e attr_writer. Isso foi usado para acessar o valor do atributo nome na classe Coisa:

attr_accessor :descricao

É equivalente a:

attr_reader :descricao
attr_writer :descricao

%RED_COLOR% continuar a partir daqui %END_COLOR%

Atributos Criam Variáveis

Recentemente eu disse que chamar attr_reader com um símbolo cria uma variável com o mesmo nome do símbolo. O método attr_accessor também faz isso. No código da classe Thing, este comportamento não é óbvio já que a classe tem um método initialize que explicitamente cria as variáveis. A classe Treasure, porém, não faz referência à variável @value no seu método initialize: class Treasure < Thing attr_accessor :value Pág. 37 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe def initialize( aName, aDescription ) super( aName, aDescription ) end end A única indicação que uma variável @value existe é a definição do acessor que declara um atributo value: attr_accessor :value Meu código no final do arquivo fonte atribue o valor de cada objeto Treasure: t1.value = 800 O atributo value não foi formalmente declarado, a variável @value realmente não existe e nós podemos recuperar o seu valor numérico usando o acessor get: t1.value Para ter certeza que o acessor do atributo realmente criou @value, você pode inspecionar o objeto usando o método inspect. Eu fiz isso no final do código fonte com as duas linhas finais do programa: puts "This is treasure1: #{t1.inspect}" puts "This is treasure2: #{t2.inspect}" accessor s3.r b # Ruby Sample program from www.sapphiresteel.com / www.bitwisemag.com # more on reading and writing attributes class Thing attr_reader :name, :description attr_writer(:name, :description) attr_accessor(:value, :id, :owner) end t = Thing.new Pág. 38 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe t.name = "A Thing" t.description = "A soft, furry wotsit" t.value = 100 t.id = "TH100SFW" t.owner = "Me" puts("#{t.name} is #{t.description}, it is worth $#(t.value)") puts("it's id is #{t.id}. It is owned by #{t.owner}.") Acessores de Atributo podem inicializar mais que um atributo por vez se você enviar a eles uma lista de símbolos na forma de argumentos separados por vírgulas, como este exemplo: attr_reader :name, :description attr_writer(:name, :description) attr_accessor(:value, :id, :owner) Como sempre, no Ruby, os parênteses em volta dos argumentos são opcionais. adventur e2.r b # Ruby Sample program from www.sapphiresteel.com / www.bitwisemag.com # illustrates how to creating descendent objects # reading and writing attributes # object (instance) variables # class variables # Thing class Thing @@num_things = 0 # class variable

attr_reader( :name, :description ) attr_writer( :description )

def initialize( aName, aDescription ) @name = aName @description = aDescription @@num_things +=1 # increment @@num_things end

def to_s # override default to_s method return "(Thing.to_s):: The #{@name} Thing is #{@description}" Pág. 39 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe end

def show_classvars return "There are #{@@num_things} Thing objects in this game" end

end

# Room class Room < Thing # TODO: Add Room­specific behaviour end # Treasure class Treasure < Thing attr_reader :value attr_writer :value

def initialize( aName, aDescription, aValue ) super( aName, aDescription ) @value = aValue end end

# Map class Map # @rooms will be an array ­ an ordered list # of Room objects def initialize( someRooms ) @rooms = someRooms end

# The to_s method iterates over all the Room objects in @rooms # and prints information on each. We'll come back to look at the # implementation of this method later on def to_s @rooms.each {
a_room
puts(a_room) } end Pág. 40 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe end

# First create a few objects # i) Treasures t1 = Treasure.new("Sword", "an Elvish weapon forged of gold",800) t2 = Treasure.new("Dragon Horde", "a huge pile of jewels", 550) # ii) Rooms room1 = Room.new("Crystal Grotto", "A glittery cavern") room2 = Room.new("Dark Cave", "A gloomy hole in the rocks") room3 = Room.new("Forest Glade", "A verdant clearing filled with shimmering light") # iii) a Map ­ which is an array containing the Rooms just created mymap = Map.new([room1,room2,room3]) # Now let's take a look at what we've got… puts "\nLet's inspect the treasures..." puts "This is the treasure1: #{t1.inspect}" puts "This is the treasure2: #{t2.inspect}" puts "\nLet's try out the Thing.to_s method..." puts "Yup, treasure 2 is #{t2.to_s}" puts "\nNow let's see how our attribute accessors work" puts "We'll evaluate this:" puts 't1 name=#{t1.name}, description=#{t1.description}, value=#{t1.value}' puts "t1 name=#{t1.name}, description=#{t1.description}, value=#{t1.value}" puts "\nNow we'll assign 100 to t1.value and alter t1.description..." t1.value = 100 t1.description << " (now somewhat tarnished)" # note << appends specified string to existing string puts "t1 (NOW) name=#{t1.name}, description=#{t1.description}, value=#{t1.value}" puts "\nLet's take a look at room1..." puts "room1 name=#{room1.name}, description=#{room1.description}" puts "\nAnd the map..." puts "mymap = #{mymap.to_s}" puts "\nFinally, let's check how many Thing objects we've created..." puts( t1.show_classvars )

# As an exercise, try adding a class variable to the Map class to Pág. 41 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe maintain # a count of the total number of rooms that have been created Agora vamos ver como colocar readers e writers de atributos para usar no meu jogo de aventura. Carregue o programa adventure2.rb (listagem acima). Você verá que eu criei dois atributos na classe Thing: name e description. Eu também fiz o atributo description poder ser atribuído pelo programador (attr_writer); porém, eu não planejo mudar os nomes dos objetos criados a partir da classe Thing, por isso o atributo name tem somente o acessor attr_reader. attr_reader( :name, :description ) attr_writer( :description ) Eu criei um método chamado to_s que retorna a string descrevendo o objeto Treasure. Lembre que toda classe Ruby têm um método to_s padrão. O método to_s na classe Thing sobrescreve ( substitui ) o padrão. Você pode sobrescrever métodos existentes quando você implementa um novo comportamento apropriado para o tipo específico da classe.

Chamando métodos de uma superclasse

Eu decidi que meu jogo terá duas classes descendendo de Thing. A classe Treasure adiciona um atribute value que pode ser lido e escrito. Note que o método initialize chama sua superclasse para inicializar os atributos name e description antes de inicializar a nova variável @value: super( aName, aDescription ) @value = aValue Aqui, se eu omitisse a chamada para a superclasse, os atributos name e description nunca seriam inicializados. Isto é porque Treasure.initialize sobrescreve Thing.initialize; logo quando um objeto Treasure é criado, o código em Thing.initialize nunca será automaticamente executado. Em alguns livr os de Ruby, um sinal # pode ser mostr ado entr e o nome da classe e o nome do método, assim: Tr easur e#initialize. Isto é apenas uma convenção de documentação ( a qual eu pr efir o ignor ar ) e não é uma sintaxe r eal Ruby. Eu acho é somente o caso de “You say tomayto and I say tomahto”; você diz Tr easur e#initialize e eu digo Tr easur e.initialize”. Não vamos br igar por isso …! Pág. 42 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe De outro lado, a classe Room, que também descende de Thing, não tem um método initialize; logo quando um novo objeto Room é criado o Ruby volta­se para para a hierarquia de classes ascendentes para procurar um initalize. O primeiro método initialize que ele encontra está na classe Thing; assim os atributos name e description do objeto Room são inicializados lá.

Variáveis de Classe

Existem algumas outras coisas interessantes neste programa. No topo da classe Thing você verá isso: @@num_things = 0 Os dois caracteres @ no início do nome da variável, @@num_things, definem que essa é uma 'variável de classe'. As variáveis que nós usamos dentro das classes até agora eram 'variáveis de instância', precedidas por um único @, como @name. Uma vez que um novo objeto (ou ‘instância’) de uma classe atribue seus próprios valores para suas próprias variáveis de instância, todos os objetos derivados de uma classe específica compartilham as mesmas variáveis da classe. Eu atribui 0 para a variável @@num_things para assegurar que ela tenha um valor significativo para o exterior. Aqui, a variável de classe @@num_things é usada para manter o valor corrente do número de objetos Thing no jogo. Isto é feito pelo incremento da variável de classe ( usamos += para adicionar 1 ao número de objetos ) no método initialize cada vez que um novo objeto é criado: @@num_things +=1 Se você olhar na parte de baixo do meu código, você verá que eu criei uma classe Map que contém uma matriz de salas ( objetos Room ). Inclui uma versão do método to_s que imprime informação sobre cada sala na matriz. Não se preocupe com a implementação da classe Map; nós veremos arrays e seus métodos brevemente. Pág. 43 Pequeno Livro do Ruby Cap. 4 ­ Acessores, Atributos, Variáveis de Classe O diagr ama acima mostr a a classe Thing (o r etângulo) que contém uma variável de classe, @@num_things e uma var iável de instância, @name, As tr ês ovais r epr esentam 'objetos Thing’ – isto é, ‘instâncias’ da classe Thing. Quando um destes objetos atr ibue um valor par a sua var iável de instância, @name, este valor afeta somente a var iável @name no pr ó­ pr io objeto – então aqui, cada objeto têm um valor difer ente par a @name. Mas quando um objeto atr ibue um valor par a a var iável de classe, @@num_things, aquele valor 'vive dentr o' da classe Thing e é compar ti­ lhado por todas as instâncias desta classe. Aqui @@num_things é igual a
          1. e que é igual par a todos os objetos Thing.
Ache o código no final do arquivo e rode o programa para ver como nós criamos e inicializamos todos os objetos e usamos a variável de classe, @@num_things, para manter a contagem de todos os objetos Thing que foram criados.

Anterior Índice Próximo

-- LeandroNunes - 18 Oct 2006
Topic revision: r1 - 05 Jul 2008, UnknownUser
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Wiki-Colivre? Send feedback