Dissimulation en rubis. Masquer également les classes de niveau supérieur

Afin de ne pas aller loin, nous définirons immédiatement les termes.


  • Encapsulation - regroupement des données et des fonctions dans un seul composant.
  • La dissimulation - est un principe de conception, qui consiste à différencier l'accès des différentes parties du programme aux composants internes les uns des autres.

Tiré du wiki . Dans le langage de programmation Ruby avec encapsulation, tout semble aller bien. Avec le masquage à première vue également, des variables locales, des variables d'instance, différents niveaux d'accès aux méthodes ( public , protected , private ) sont à notre disposition. Mais parfois, cela peut ne pas suffire.


Prenons l'exemple suivant.


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

Nous déclarons la classe Address dans User , nous supposons qu'il ne s'agit pas simplement d'une adresse abstraite, mais d'une adresse avec une logique spécifique qui n'est nécessaire que dans le contexte des objets User . Et de plus, nous ne voulons pas que cette Address soit accessible de n'importe où dans le programme, c'est-à-dire non seulement l'encapsuler dans l' User , mais aussi le masquer pour tous les autres objets. Comment faire


Vous pouvez l'essayer en private .


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

pry et exécutons par exemple dans pry et obtenons:


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

Ainsi, nous vérifions que le modificateur private dans ce contexte ne fonctionne pas. Mais il y a juste une méthode magique private_constant qui fonctionnera comme il se doit. Après tout, les classes en rubis sont également des constantes. Maintenant, nous pouvons écrire private_constant :Address et intercepter une erreur en essayant d'accéder à User::Address :


NameError: private constant User::Address referenced


Maintenant, nous posons le problème plus compliqué. Ajoutez une classe de mise en cache qui utilisera redis.


 #shared_cache.rb require 'redis' class SharedCache end 

Et il semble que rien n'annonce de problème, jusqu'à ce que quelque part au milieu de la vue, à l'intérieur du modèle erb, quelqu'un ne veuille pas écrire redis.set redis.get \ redis.set , contournant même SharedCache. Nous traitons comme suit:


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

Qu'est-il arrivé? Par un appel à remove_const nous remove_const Redis de la visibilité réelle des objets de niveau supérieur. Mais avant ceux-ci, nous avons mis Redis dans SharedCache . De plus, nous pouvons restreindre l'accès à SharedCache::Redis via private_constant . Cependant, dans ce cas, nous ne pourrons plus accéder à la classe Redis de quelque manière que ce soit, même si nous voulons l'utiliser ailleurs. Nous ennoblissons et laissons faire require intérieur de plusieurs 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 

Tentatives d'appeler 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>' 

Pour ce qu'il peut être utilisé:


  • Pour masquer les classes utilitaires internes dans une autre classe ou module
  • Encapsulation avec masquage de la logique à l'intérieur des classes de service - vous pouvez empêcher l'accès à certaines classes en contournant les objets de service.
  • Supprimez les classes "dangereuses" de la visibilité de niveau supérieur, par exemple, pour interdire l'accès aux bases de données depuis View ou les sérialiseurs. Dans Rails, vous pouvez «masquer» toutes les classes ActiveRecord et leur donner un accès sélectif à des endroits spécifiques.

Et un exemple d'implémentation de require_to qui déplace les constantes du niveau supérieur vers le niveau de visibilité souhaité.


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


All Articles