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:

  1. Métodos públicos (public) podem ser chamados por todos – nenhum controle de acesso é forçado. Os métodos de instância de uma classe (estes não pertencem a apenas um objeto; ao invés disso cada instância da classe pode chamá-los) são públicos por padrão; qualquer um pode chamá-los. O método initialize é sempre privado.
  2. Métodos protegidos (protected) podem ser chamados apenas por objetos da classe mesma classe que os definem e suas subclasses. O acesso é mantido na família. Entretanto, o uso de protected é limitado.
  3. Métodos privados (private) não podem ser chamados com um recebedor explícito – o recebedor é sempre self. Isso significa que podem ser chamados apenas no contexto do objeto atual; você não pode chamar o método private de outros objetos.

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.

Sobrepondo métodos privados

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.

Métodos acessores

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

Variáveis de instância são herdadas por uma subclasse?

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.

Métodos de nível alto

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.

Logo do Guru-SP

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

Voltar para o índice