Ocultamiento en Ruby. Ocultar también clases de nivel superior

Para no llegar lejos, definiremos de inmediato los términos.


  • Encapsulación: empaqueta datos y funciones en un solo componente.
  • Ocultación: es un principio de diseño, que consiste en diferenciar el acceso de las diferentes partes del programa a los componentes internos entre sí.

Tomado de la wiki . En el lenguaje de programación Ruby con encapsulación, todo parece estar bien. Al ocultarnos a primera vista, también tenemos a nuestra disposición variables locales, variables de instancia, diferentes niveles de acceso a métodos ( public , protected , private ). Pero a veces esto puede no ser suficiente.


Considere el siguiente ejemplo.


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

Declaramos la clase Address dentro de User , suponemos que no se trata solo de una dirección abstracta, sino de una dirección con lógica específica que solo se necesita en el contexto de los objetos User . Y, además, no queremos que esta Address sea ​​accesible desde cualquier parte del programa, es decir, no solo lo encapsula dentro del User , sino que también quiere ocultarlo para todos los demás objetos. Como hacerlo


Puedes probarlo en private .


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

pry y ejecutamos, por ejemplo, dentro de pry y obtenemos:


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

Por lo tanto, verificamos que el modificador private en este contexto no funciona. Pero solo hay un método mágico private_constant que funcionará como debería. Después de todo, las clases en rubí también son constantes. Ahora podemos escribir private_constant :Address y detectar un error al intentar acceder a User::Address :


NameError: private constant User::Address referenced


Ahora planteamos el problema más complicado. Agregue una clase de almacenamiento en caché que usará redis.


 #shared_cache.rb require 'redis' class SharedCache end 

Y parece que nada augura problemas, hasta que en algún lugar en el medio de la Vista, dentro de la plantilla erb, alguien no quiera escribir redis.get \ redis.set , sin pasar por alto incluso SharedCache. Tratamos de la siguiente manera:


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

Que paso Mediante una llamada a remove_const eliminamos Redis de la visibilidad real de nivel superior de los objetos. Pero antes de esto, colocamos Redis dentro de SharedCache . Además, podemos restringir el acceso a SharedCache::Redis través de private_constant . Sin embargo, en este caso, ya no podremos llegar a la clase Redis de ninguna manera, incluso si queremos usarla en otro lugar. Ennoblecemos y permitimos require dentro de varias clases:


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

Intenta llamar a 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 qué se puede usar:


  • Para ocultar clases de utilidad internas dentro de otra clase o módulo.
  • Encapsulación con lógica oculta dentro de las clases de servicio: puede evitar el acceso a algunas clases sin pasar por los objetos de servicio.
  • Elimine las clases "peligrosas" de la visibilidad de nivel superior, por ejemplo, para prohibir el acceso a las bases de datos desde View o serializadores. En Rails, puede "ocultar" todas las clases de ActiveRecord y darles acceso selectivo a lugares específicos.

Y un ejemplo de implementación de require_to que mueve las constantes desde el nivel superior al nivel de visibilidad deseado.


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/es419969/


All Articles