web-dev-qa-db-fra.com

Comment rendre les variables d'instance privées dans Ruby?

Existe-t-il un moyen de rendre les variables d'instance "privées" (C++ ou Java) en ruby? En d'autres termes, je veux que le code suivant entraîne une erreur.

class Base
  def initialize()
    @x = 10
  end
end

class Derived < Base
  def x
    @x = 20
  end
end

d = Derived.new
34
prasadvk

Comme la plupart des choses dans Ruby, les variables d'instance ne sont pas vraiment "privées" et sont accessibles à toute personne possédant d.instance_variable_get :@x.

Contrairement à Java/C++, cependant, les variables d'instance dans Ruby sont toujours privées. Elles ne font jamais partie de l'API publique comme les méthodes, car elles ne sont accessibles que avec ce getter verbeux. Donc, s'il y a un bon sens dans votre API, vous n'avez pas à vous soucier de quelqu'un qui abuse de vos variables d'instance, car ils utiliseront les méthodes à la place. (Bien sûr, si quelqu'un veut se déchaîner et accéder à méthodes privées ou variables d'instance, il n'y a aucun moyen de les arrêter.)

Le seul problème est que quelqu'un accidentellement écrase une variable d'instance quand il étend votre classe. Cela peut être évité en utilisant des noms improbables, peut-être en l'appelant @base_x dans votre exemple.

35
Josh Lee

N'utilisez jamais de variables d'instance directement. N'utilisez que des accessoires. Vous pouvez définir le lecteur comme public et le rédacteur privé par:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

Cependant, gardez à l'esprit que private et protected ne signifient pas ce que vous pensez qu'ils signifient. Les méthodes publiques peuvent être appelées contre n'importe quel récepteur: nommé, self ou implicite (x.baz, self.baz ou baz). Les méthodes protégées ne peuvent être appelées qu'avec un récepteur de soi ou implicitement (self.baz, baz). Les méthodes privées ne peuvent être appelées qu'avec un récepteur implicite (baz).

En bref, vous abordez le problème d'un point de vue non Ruby. Utilisez toujours des accesseurs au lieu des variables d'instance. Utilisez public/protected/private pour documenter votre intention et supposez que les consommateurs de votre API sont des adultes responsables.

26
Stephen Touset

Il est possible (mais déconseillé) de faire exactement ce que vous demandez.

Il existe deux éléments différents du comportement souhaité. Le premier stocke x dans un valeur en lecture seule, et le second est protégeant le getter contre les modifications dans les sous-classes.


valeur en lecture seule

Il est possible dans Ruby de stocker des valeurs en lecture seule au moment de l'initialisation. Pour ce faire, nous utilisons le comportement de fermeture des blocs Ruby.

class Foo
  def initialize (x)
    define_singleton_method(:x) { x }
  end
end

La valeur initiale de x est maintenant verrouillée à l'intérieur du bloc que nous avons utilisé pour définir le getter #x et n'est jamais accessible sauf en appelant foo.x, et il ne peut jamais être modifié.

foo = Foo.new(2)
foo.x  # => 2
foo.instance_variable_get(:@x)  # => nil

Notez qu'il n'est pas stocké en tant que variable d'instance @x, mais il est toujours disponible via le getter que nous avons créé à l'aide de define_singleton_method.


Protéger le getter

Dans Ruby, presque toutes les méthodes de n'importe quelle classe peuvent être écrasées lors de l'exécution. Il existe un moyen d'éviter cela en utilisant le method_added crochet.

class Foo
  def self.method_added (name)
    raise(NameError, "cannot change x getter") if name == :x
  end
end

class Bar < Foo
  def x
    20
  end
end

# => NameError: cannot change x getter

Il s'agit d'une méthode très lourde pour protéger le getter.

Cela nécessite que nous ajoutions chaque getter protégé au method_added hook individuellement, et même dans ce cas, vous devrez ajouter un autre niveau de method_added protection contre Foo et ses sous-classes pour empêcher un codeur d'écraser method_added méthode elle-même.

Mieux vaut accepter le fait que le remplacement de code à l'exécution est une réalité de la vie lors de l'utilisation de Ruby.

13
user513951

Contrairement aux méthodes ayant différents niveaux de visibilité, les variables d'instance Ruby sont toujours privées (depuis l'extérieur des objets). Cependant, les variables d'instance à l'intérieur des objets sont toujours accessibles, soit à partir du parent, de la classe enfant ou des modules inclus. .

Puisqu'il n'y a probablement aucun moyen de modifier la façon dont Ruby access @x, Je ne pense pas que vous puissiez avoir le moindre contrôle dessus. L'écriture @x choisirait juste directement cette variable d'instance, et puisque Ruby ne fournit pas de contrôle de visibilité sur les variables, je vis avec.

Comme le dit @marcgg, si vous ne voulez pas que les classes dérivées touchent vos variables d'instance, ne l'utilisez pas du tout ou trouvez un moyen intelligent de le cacher aux classes dérivées.

5
bryantsai

Il n'est pas possible de faire ce que vous voulez, car les variables d'instance ne sont pas définies par la classe, mais par l'objet.

Si vous utilisez la composition plutôt que l'héritage, vous n'aurez pas à vous soucier du remplacement des variables d'instance.

1
Andrew Grimm