Ocultação em Ruby. Também oculte classes do nível superior

Para não ir longe, definiremos imediatamente os termos.


  • Encapsulamento - empacotando dados e funções em um único componente.
  • Ocultação - é um princípio de design, que consiste em diferenciar o acesso de diferentes partes do programa aos componentes internos um do outro.

Retirado do wiki . Na linguagem de programação Ruby com encapsulamento, tudo parece estar bem. Escondendo à primeira vista também, variáveis ​​locais, variáveis ​​de instância, diferentes níveis de acesso a métodos ( public , protected , private ) estão disponíveis para nós. Mas às vezes isso pode não ser suficiente.


Considere o seguinte exemplo.


 class User class Address < String def ==(other_object) #   end end def initialize(name:, address: nil) @name = name @address = Address.new(address) end end 

Declaramos a classe Address dentro de User , assumimos que este não é apenas um endereço abstrato, mas um endereço com lógica específica que é necessária apenas no contexto dos objetos User . Além disso, não queremos que este Address seja acessível de qualquer lugar do programa, ou seja, não apenas o encapsula dentro do User , mas também deseja ocultá-lo para todos os outros objetos. Como fazer isso?


Você pode experimentá-lo através do private .


 class User private class Address < String def ==(other_object) #   end end end 

pry e executamos, por exemplo, dentro do pry e obtemos:


 User::Address => User::Address User::Address.new => "" 

Assim, verificamos que o modificador private nesse contexto não funciona. Mas existe apenas um método private_constant mágico que funcionará como deveria. Afinal, as classes em ruby ​​também são constantes. Agora podemos escrever o private_constant :Address e capturar um erro ao tentar acessar o User::Address :


NameError: private constant User::Address referenced


Agora, colocamos o problema mais complicado. Adicione uma classe de cache que usará redis.


 #shared_cache.rb require 'redis' class SharedCache end 

E parece que nada indica problemas, até que em algum lugar no meio do View, dentro do modelo erb, alguém não queira escrever redis.get \ redis.set , ignorando até o SharedCache. Tratamos da seguinte forma:


 require 'redis' SharedCache.send :const_set, :Redis, Redis Object.send :remove_const, :Redis Redis NameError: uninitialized constant Redis from (pry):7:in `__pry__' 

O que aconteceu Por meio de uma chamada para remove_const removemos o Redis da visibilidade real dos objetos de nível superior. Mas antes disso, colocamos o Redis dentro do SharedCache . Além disso, podemos restringir o acesso ao SharedCache::Redis através de private_constant . No entanto, nesse caso, não poderemos mais alcançar a classe Redis nenhuma maneira, mesmo se quisermos usá-la em outro lugar. Nós enobrecemos e permitimos que require várias classes:


 class SharedCache require_to 'redis', :Redis private_constant :Redis def storage Redis end end class SharedCache2 require_to 'redis', :Redis private_constant :Redis end 

Tentativas de ligar para Redis:


 [1] pry(main)> SharedCache::Redis NameError: private constant SharedCache::Redis referenced from (pry):1:in `<main>' [2] pry(main)> require 'redis' => false [3] pry(main)> Redis NameError: uninitialized constant Redis from (pry):6:in `<main>' [4] pry(main)> SharedCache.new.storage => Redis [5] pry(main)> SharedCache2::Redis NameError: private constant SharedCache2::Redis referenced from (pry):1:in `<main>' 

Para o que pode ser usado:


  • Ocultar classes de utilitários internos dentro de outra classe ou módulo.
  • Encapsulamento com ocultação de lógica dentro de classes de serviço - você pode impedir o acesso a algumas classes ignorando objetos de serviço.
  • Remova as classes "perigosas" da visibilidade de nível superior, por exemplo, para proibir o acesso aos bancos de dados do View ou serializadores. No Rails, você pode "ocultar" todas as classes do ActiveRecord e dar a elas acesso seletivo a locais específicos.

E um exemplo de implementação de require_to que move constantes do Nível Superior para o nível de visibilidade desejado.


require_to
 class Object def const_hide sym, obj _hidden_consts.const_set sym, obj Object.send :remove_const, sym end def hidden_constants _hidden_consts.constants end def hidden_const sym _hidden_consts.const_get sym end def require_to(name, sym, to: nil) require name if Object.const_defined? sym obj = Object.const_get sym const_hide sym, obj else obj = hidden_const sym end (to || self).const_set sym, obj end private def _hidden_consts @@_hidden_consts ||= Class.new end end 

Source: https://habr.com/ru/post/pt419969/


All Articles