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)
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)
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.
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