2010-03-14 2 views
13

Étant donné le code Java ci-dessous, de quelle façon pouvez-vous représenter ces deux variables static final dans une classe Ruby? Et, est-il possible dans Ruby de faire la distinction entre private static et public static variables comme il y a en Java?Représentation Ruby la plus proche d'une variable de classe 'private static final' et 'public static final' en Java?

public class DeviceController 
{ 
    ... 
    private static final Device myPrivateDevice = Device.getDevice("mydevice"); 
    public static final Device myPublicDevice = Device.getDevice("mydevice"); 
    ... 
    public static void main(String args[]) 
    { 
    ... 
    } 
} 
+2

AFAIK il n'y a pas des constantes réelles (finales) en Ruby –

+3

mais on peut utiliser 'SOME_CONSTANT.freeze' pour faire un objet immuable – clyfe

+0

Que diriez-vous cette solution http://stackoverflow.com/a/18168745/665967 –

Répondre

42

Il n'y a vraiment pas d'équivalent en construction Ruby.

Cependant, il semble que vous faites l'une des erreurs de portage classique: vous avez une solution en langage A et essayer de traduire en langage B, quand ce que vous devriez vraiment faire est de déterminer le problème et puis comprendre comment le résoudre dans la langue B.

Je ne peux pas vraiment être sûr quel est le problème que vous essayez de résoudre à partir de ce petit code codes, mais voici un idée possible pour la mettre en œuvre en Ruby:

class DeviceController 
    class << self 
    def my_public_device; @my_public_device ||= Device['mydevice'] end 

    private 

    def my_private_device; @my_private_device ||= Device['mydevice'] end 
    end 
end 

Voici une autre:

class DeviceController 
    @my_public_device ||= Device['mydevice'] 
    @my_private_device ||= Device['mydevice'] 

    class << self 
    attr_reader :my_public_device, :my_private_device 
    private :my_private_device 
    end 
end 

(La différence est que le premier exemple est paresseux, il initialise que la variable d'instance lorsque le lecteur d'attribut correspondant est d'abord appelé. Le second les initialise dès que le corps de la classe est exécuté, même s'ils ne sont jamais nécessaires, tout comme la version Java.)

Reprenons quelques concepts ici. Dans Ruby, comme dans tous les autres langages "appropriés" (pour diverses définitions du langage "propre"), l'état (variables d'instance, champs, propriétés, emplacements, attributs, peu importe comment vous les appelez) est toujours privé. Il n'y a aucun pour y accéder de l'extérieur. La seule façon de communiquer avec un objet est de lui envoyer des messages. [Note: Chaque fois que j'écris quelque chose comme "pas d'accord", "toujours", "la seule façon" etc., cela ne signifie en réalité "aucun moyen, sauf pour la réflexion". En d'autres termes: dans Ruby, les variables sont toujours privées, la seule façon d'y accéder est par une méthode getter et/ou setter, ou, comme elles le sont appelé dans Ruby, un lecteur d'attributs et/ou écrivain.

Maintenant, je continue à écrire sur variables d'instance, mais dans l'exemple Java, nous avons champs statiques, à savoir variables de classe. Eh bien, dans Ruby, contrairement à Java, les classes sont aussi des objets. Ce sont des instances de la classe Class et ainsi, comme tout autre objet, ils peuvent avoir des variables d'instance. Donc, dans Ruby, l'équivalent d'une variable de classe est vraiment juste une variable d'instance standard qui appartient à un objet qui se trouve être une classe. Il existe également des variables de hiérarchie de classes, notées avec un double au signe @@sigil.Celles-ci sont vraiment étranges, et vous devriez probablement les ignorer.Les variables de hiérarchie de classe sont partagées dans toute la hiérarchie de classe, à savoir la classe à laquelle elles appartiennent , toutes ses sous-classes et leurs sous-classes et leurs sous-classes ... et aussi toutes les instances de toutes ces classes.En fait, elles ressemblent plus à des variables globales qu'à des variables de classe.Ils devraient vraiment s'appeler $$var au lieu de @@var, puisqu'ils sont beaucoup plus étroitement liés aux variables globales plutôt qu'aux variables d'instance, ils ne sont pas totalement inutiles mais très rarement utiles.)

Donc, nous avons couvert la partie "champ" (champ Java == R uby instance variable), nous avons couvert les parties "public" et "private" (dans Ruby, les variables d'instance sont toujours privées, si vous voulez les rendre publiques, utilisez une méthode publique getter/setter) et nous avons couvert "partie (Java static field == variable d'instance de la classe Ruby).Qu'en est-il de la partie "finale"? En Java, "final" est juste une façon amusante d'orthographe "const", que les concepteurs ont évité car le mot-clé const dans des langages comme C et C++ est subtilement cassé et ils ne voulaient pas confondre les gens. Ruby a ont des constantes (notées en commençant par une lettre majuscule). Malheureusement, ils ne sont pas vraiment constants, car essayer de les modifier, tout en générant un avertissement, fonctionne réellement. Donc, ils sont plus d'une convention qu'une règle imposée par un compilateur. Cependant, la restriction la plus importante des constantes est qu'elles sont toujours publiques.

Ainsi, les constantes sont presque parfaits: ils ne peuvent pas être modifiés (bien, ils ne doivent pas être modifiés), à savoir qu'ils sont final, ils appartiennent à une classe (ou module), à ​​savoir qu'ils sont static. Mais ils sont toujours public, donc malheureusement, ils ne peuvent pas être utilisés pour modéliser les champs private static final.

Et c'est exactement le point où la réflexion sur les problèmes au lieu de solutions intervient. Qu'est-ce que vous voulez? Vous voulez affirment que

  1. appartient à une classe,
  2. ne peut être lu pas écrit,
  3. est initialisées une fois
  4. peut être privé ou public.

Vous pouvez réaliser tout cela, mais d'une manière complètement différente de celle de Java:

  1. ne fournissent pas une instance de classe variable de
  2. une méthode setter, seul un getter
  3. utilisation l'affectation composée ||= de Ruby pour assigner une seule fois
  4. getter

La seule chose dont vous avez à vous soucier, c'est que vous n'attribuez pas à @my_public_device n'importe où, ou mieux encore, n'y accédez pas du tout. Utilisez toujours la méthode getter.

Oui, ce est un trou dans l'implémentation. Ruby est souvent appelé un "langage adulte" ou un "langage adulte consentant", ce qui signifie qu'au lieu d'imposer certaines choses au compilateur, il suffit de les mettre dans la documentation et de croire que les autres développeurs ont appris que toucher les autres les soldats des gens est rude ...


un totalement approche différente à la vie privée est celle qui est utilisée dans les langages fonctionnels: les fermetures d'utilisation. Les fermetures sont des blocs de code qui ferment sur leur environnement lexical, même après que l'environnement lexical est hors de portée. Cette méthode de mise en œuvre d'un état privé est très populaire dans Scheme, mais a récemment été popularisée par Douglas Crockford et al. pour JavaScript. Voici un exemple de Ruby:

class DeviceController 
    class << self 
    my_public_device, my_private_device = Device['mydevice'], Device['mydevice'] 

    define_method :my_public_device do my_public_device end 
    define_method :my_private_device do my_private_device end 

    private :my_private_device 
    end # <- here the variables fall out of scope and can never be accessed again 
end 

Notez la différence subtile mais importante pour les versions en haut de ma réponse: le manque de @ Sigil.Ici, nous créons variables locales, pas instance variables. Dès que le corps de la classe se termine, ces variables locales tombent hors de portée et ne peuvent plus jamais être accessibles. Seuls les deux blocs qui définissent les deux méthodes getter ont toujours accès à eux, car ils ferment sur le corps de la classe. Maintenant, ils sont vraiment privé et ils sont final, parce que la seule chose dans l'ensemble du programme qui a toujours accès à eux est une pure méthode getter.

Ceci n'est probablement pas idiomatique Ruby, mais pour toute personne ayant un arrière-plan Lisp ou JavaScript, il devrait être assez clair. C'est aussi très élégant.

+0

Bonne réponse! Dans le dernier exemple, l'utilisation de * || = * semble redondante, et une affectation simple devrait suffire? –

+0

@Lars Haugseth: Oui. Copier et coller l'erreur. –

1
class DeviceController 
    MY_DEVICE = Device.get_device("mydevice") 
end 

Et oui, require 'device' si nécessaire.

Bien que rien ne vous empêche de redéfinir la constante ailleurs, à l'exception d'un avertissement :)

+0

réellement * est * public :) vous pouvez faire DeviceController :: MY_DEVICE de tous les endroits dans votre programme. Je ne sais pas comment le rendre privé cependant. –

+0

constantes privées ont été parlées pour Ruby 2.0. –

0

statique privé dans Ruby:

class DeviceController 
    @@my_device = Device.get_device("mydevice") 
end 

public static en Ruby:

class DeviceController  
    def self.my_device; @@my_device; end 

    @@my_device = Device.get_device("mydevice") 
end 

Ruby ne peut avoir aucun 'final' :)

3

La chose la plus proche que je peux penser à une finale variable est de mettre la variable en question comme une variable d'instance d'un module:

class Device 
    # Some static method to obtain the device 
    def self.get_device(dev_name) 
     # Need to return something that is always the same for the same argument 
     dev_name 
    end 
end 

module FinalDevice 
    def get_device 
     # Store the device as an instance variable of this module... 
     # The instance variable is not directly available to a class that 
     # includes this module. 
     @fin ||= Device.get_device(:my_device).freeze 
    end 
end 

class Foo 
    include FinalDevice 
    def initialize 
     # Creating an instance variable here to demonstrate that an 
     # instance of Foo cannot see the instance variable in FinalDevice, 
     # but it can still see its own instance variables (of course). 
     @my_instance_var = 1 
    end 
end 

p Foo.new 

p (Foo.new.get_device == Foo.new.get_device) 

Ce sorties:

#<Foo:0xb78a74f8 @my_instance_var=1> 
true 

L'astuce ici est que par l'encapsulation du dispositif dans un module, vous ne pouvez accéder l'appareil à travers ce module. De la classe Foo, il n'y a aucun moyen de modifier quel périphérique vous accédez, sans agir directement sur la classe Device ou le module FinalDevice. L'appel freeze au FinalDevice peut être approprié ou non, selon vos besoins.

Si vous voulez faire un accesseur public et privé, vous pouvez modifier Foo comme ceci:

class Foo 
    include FinalDevice 

    def initialize 
     @my_instance_var = 1 
    end 

    def get_device_public 
     get_device 
    end 

    private 
    def get_device_private 
     get_device 
    end 

    private :get_device 
end 

Dans ce cas, vous aurez probablement besoin de modifier FinalDevice::get_device prendre un argument aussi bien.

Mise à jour: @banister a souligné que @fin comme déclaré dans FinalDevice est en effet accessible par une instance de Foo. J'avais paresseusement supposé que puisque ce n'était pas dans le texte par défaut produit par Foo#inspect, il n'était pas à l'intérieur Foo.

Vous pouvez remédier à cela en plus explicitement faire @fin une variable d'instance du module FinalDevice:

class Device 
    def self.get_device(dev_name) 
     dev_name 
    end 
end 

module FinalDevice 
    def get_device 
     FinalDevice::_get_device 
    end 

    protected 
    def self._get_device 
     @fin ||= Device.get_device(:my_device).freeze 
    end 
end 

class Foo 
    include FinalDevice 

    def get_device_public 
     get_device 
    end 

    def change_fin 
     @fin = 6 
     @@fin = 8 
    end 

    private 
    def get_device_private 
     get_device 
    end 

    private :get_device 
end 

f = Foo.new 
x = f.get_device_public 
f.change_fin 
puts("fin was #{x}, now it is #{f.get_device_public}") 

qui produit correctement:

fin was my_device, now it is my_device 
+0

+1, très élégant. Aussi, merci d'avoir inspiré ma solution basée sur la fermeture! –

+0

que voulez-vous dire l'ivar n'est pas directement disponible dans la classe? si vous définissez une méthode sur Foo comme 'def change_fin; @fin = 100; end' puis exécutez 'f = Foo.new; f.change_fin; f.get_device_public' le retour sera 100. Vous pouvez simplement accéder à @fin dans la classe et il change _exactement_ le même @fin qui est défini dans le module. Un objet n'a qu'une seule table ivar. – horseyguy

+0

@banister: vous aviez raison; J'ai mis à jour le code pour utiliser explicitement une variable d'instance du module 'FinalDevice'. J'avais paresseusement supposé que puisque '@ fin' ne s'affichait pas dans' p Foo.new', il était déjà contenu dans le module 'FinalDevice'. Mais maintenant, il est évident que cela n'a pas encore été déclaré. Merci. –

Questions connexes