Controle de acesso
O único jeito fácil de se mudar o estado de um objeto em Ruby é chamando um de seus métodos. Controle o acesso a métodos e você terá controlado o acesso a objetos. Uma boa regra é nunca expor métodos que podem deixar um objeto em um estado inválido.
Ruby lhe dá três níveis de proteção:
O controle de acesso é determinado dinamicamente, a medida que o programa executa e não estaticamente. Você terá uma violação de acesso apenas quando o código tenta executar o método restrito. Observemos o programa p047classaccess.rb abaixo:
1 # p047classaccess.rb 2 class AcessoClasse 3 def m1 # este método é público 4 end 5 protected 6 def m2 # este método é protegido 7 end 8 private 9 def m3 # este método é privado 10 end 11 end 12 ca = AcesoClasse.new 13 ca.m1 14 #ca.m2 15 #ca.m3
Se você remover os comentários das duas últimas declarações no programa acima, você obterá um erro de violação de acesso (access violation runtime error)
Alternativamente, você pode configurar os níveis de acesso de métodos listando-os como argumentos para as funções de acesso de controle.
1 class AcessoClasse 2 def m1 # este método é público 3 end 4 public :m1 5 protected :m2, :m3 6 private :m4, :m5 7 end 8
Aqui está um exemplo para o controle de acesso protegido (p047zclassaccess.rb):
1 # p047zclassaccess.rb 2 class Pessoa 3 def initialize(idade) 4 @idade = idade 5 end 6 def idade 7 @idade 8 end 9 def compara_idade(c) 10 if c.idade > idade 11 "A idade do outro objeto é maior." 12 else 13 "A idade do outro objeto é menor." 14 end 15 end 16 protected :idade 17 end 18 19 chris = Pessoa.new(25) 20 marcos = Pessoa.new(34) 21 puts chris.compara_idade(marcos) 22 #puts chris.idade
A saída é
1 >ruby p047zclassaccess.rb 2 A idade do outro objeto é maior. 3 >Exit code: 0
No exemplo anterior, comparamos uma instância de Pessoa com outra instância de pessoa. A comparação, entretanto, depende do resultado de uma chamada ao método idade. O objeto que faz a comparação (chris, no exemplo) precisa perguntar ao outro objeto (marcos) para executar seu método idade. Portanto, idade não pode ser privado.
É aí que entra o nível de proteção protected. Com idade protegida ao invés de privada, chris pode perguntar a marcos para executar idade, porque chris e marcos são ambos instâncias da mesma classe. Mas se você tentar chamar o método idade de um objeto Pessoa quando self é qualquer outra coisa que não um objeto do tipo Pessoa, o método falhará.
Um método protegido é então como um método privado, mas com uma exceção para casos em que a classe de self (chris) e a classe do objeto tendo o método chamado em si (marcos) são a mesma.
Atente-se que, se você remover o comentário da última declaração do programa, isso é, quando você usa idade diretamente, o Ruby lança uma exceção.
No Ruby, público, privado e protegido se aplicam apenas a métodos. Instâncias e variáveis de classe são encapsuladas e efetivamente privadas e constantes são efetivamente públicas. Não há nenhum modo de fazer uma variável de instância acessível de fora de uma classe (exceto quando se usa um método acessor). E não há como definir uma constante que seja inacessível para uso externo.
Métodos privados não podem ser chamados de fora da classe que os defina. Mas eles são herdados por subclasses. Isto significa que estas subclasses podem chamá-los e podem sobrepô-los.
Classes frequentemente usam métodos privados como métodos de ajuda internos. Eles não são parte da API pública da classe e não são planejados para que sejam visíveis. Se você, por ventura, definir um método em sua subclasse que tem o mesmo nome de um método privado na superclasse, você terá substituído o método de utilidade interno à superclasse, e isso irá provavelmente causar um comportamento não desejado.
O encapsulamento é obtido quando as variáveis de instância são privadas para um objeto e você tem getters e setters (no Ruby os chamamos de leitores e escritores de atributos). Para fazer variáveis de instância disponíveis, o Ruby provê métodos acessores (accessors) que retornam seus valores. O programa p048accessor.rb ilustra isso.
1 # p048accessor.rb 2 # Primeiro sem os métodos acessores 3 class Musica 4 def initialize(nome, artista) 5 @nome = nome 6 @artista = artista 7 end 8 def nome 9 @nome 10 end 11 def artista 12 @artista 13 end 14 end 15 16 musica = Musica.new("Brasil", "Ivete Sangalo") 17 puts musica.nome 18 puts musica.artista 19 20 # Agora com métodos acessores 21 class Musica 22 def initialize(nome, artista) 23 @nome = nome 24 @artista = artista 25 end 26 attr_reader :nome, :artista # cria leitor apenas 27 # Para criar métodos leitores e escritores 28 # attr_accessor :nome 29 # Para criar métodos escritores 30 # attr_writer :nome 31 32 end 33 34 musica = Musica.new("Brasil", "Ivete Sangalo") 35 puts musica.nome 36 puts musica.artista
David Black, o autor do livro Ruby for Rails, tem isso a dizer: Variáveis de instância são por objeto, não por classe, e elas não são herdadas. Mas se um método usa uma, e esse método está disponível para suas subclasses, então ele ainda usará a variável — mas “a variável” no sentido de uma por objeto. Veja o seguinte programa – p049instvarinherit.rb:
1 # p049instvarinherit.rb 2 class C 3 def initialize 4 @n = 100 5 end 6 7 def aumenta_n 8 @n *= 20 9 end 10 end 11 12 class D < C 13 def mostra_n 14 puts "n eh #{@n}" 15 end 16 end 17 18 d = D.new 19 d.aumenta_n 20 d.mostra_n
A saída é:
1 >ruby p049instvarinherit.rb 2 n eh 2000 3 >Exit code: 0
O @n nos métodos de D é o mesmo (para cada instância) que o @n em C.
Todos os objetos Ruby tem um conjunto de variáveis de instância. Estas não são definidas pela classe do objeto – elas são simplesmente criadas quando um valor é associado a elas. Pelo fato de variáveis de instância não serem definidas por uma classe, elas não são relacionadas aos mecanismos de subclasses e herança.
Quando você escreve código no nível mais alto, Ruby lhe provê automaticamente um objeto self padrão. Este objeto é uma instância direta de Object. Quando você pergunta para que ele se descreva:
1 puts self
Ele responde:
1 main
O objeto main é o objeto atual assim que seu programa inicia.
Suponhamos que você defina um método no nível mais alto:
1 def fala 2 puts "Ola" 3 end
A quem, ou o que, o método pertence? Ele não está dentro de um bloco de definição de classe nem de módulo, então ele não parece ser um método de instância de uma classe ou módulo. Ele não está associado a nenhum objeto em particular (como em def obj.fala). O que ele é? Objetos do nível mais alto são métodos de instância privados do módulo Kernel.
Por serem privados, métodos de alto nível não podem ser chamados com um recebedor explícito (o objeto antes do ponto); você pode apenas chamá-los usando o recebedor ímplicito, self. Isso significa que self precisa ser um objeto em cujo caminho de busca de métodos está o método de alto nível dado. Mas todo caminho de busca de métodos de um objeto inclui o módulo Kernel, porque a classe Objeto incorpora (mixes in) Kernel, e todo objeto de uma classe possui Object como ancestral. Isso significa que você pode sempre chamar qualquer método de nível mais alto, onde quer que você esteja em seu programa.
Isso também significa que você nunca pode usar um recebedor explícito em um método de nível mais alto.
Desde nossos primeiros exemplos até agora, temos feito chamadas simples a puts e print, como essa aqui:
1 puts 'Ola'
puts e print são métodos de instância privados inclusos em Kernel. É por isso que você pode – de fato, deve – chamá-los sem um recebedor.
Falaremos sobre self detalhadamente mais tarde.
Este material tem como base o tutorial do RubyLearning.com de Satish Talim e foi traduzido por membros do GURU-SP com a permissão do autor.
Ajude o RubyLearning participando em algum dos cursos pagos ou fazendo uma doação para o projeto