Die engste Ruby -Darstellung einer "privaten statischen Finale" und "öffentliche statische endgültige" Klassenvariable in Java?

StackOverflow https://stackoverflow.com/questions/2441524

Frage

Angesichts des folgenden Java -Code static final Variablen in einer Rubinklasse? Und ist es in Ruby möglich, zwischen zu unterscheiden private static und public static Variablen wie in 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[])
  {
   ...
  }
}
War es hilfreich?

Lösung

Es gibt wirklich kein äquivalentes Konstrukt in Ruby.

Es sieht jedoch so aus, als würden Sie einen der klassischen Portierungsfehler machen: Sie haben eine Lösung in der Sprache A und versuchen Sie, dies in Sprache B zu übersetzen, wenn Sie wirklich das herausfinden sollten Problem Und dann herausfinden, wie man es in Sprache B. lösen kann

Ich kann nicht sicher sein, was das Problem ist, dass Sie versuchen, aus diesem kleinen Codesnippet zu lösen, aber hier ist eines Mögliche Idee, wie man es in Ruby implementiert:

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

Hier ist ein anderes:

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

(Der Unterschied besteht darin, dass das erste Beispiel faul ist. Es initialisiert nur die Instanzvariable, wenn der entsprechende Attributleser zum ersten Mal aufgerufen wird. Der zweite initialisiert sie, sobald der Klassenkörper ausgeführt wird, auch wenn sie nie benötigt werden, genau wie der Java -Version tut.)

Lassen Sie uns hier einige Konzepte durchgehen.

In Ruby, wie in jedem anderen "richtigen" (für verschiedene Definitionen von "richtiger") objektorientierter Sprache, Zustand (Instanzvariablen, Felder, Eigenschaften, Slots, Attribute, wie auch immer Sie sie nennen möchten) ist stets Privatgelände. Es gibt auf keinen Fall von außen zugreifen. Die einzige Möglichkeit, mit einem Objekt zu kommunizieren, besteht darin, diese Nachrichten zu senden.

Hinweis: Wenn ich so etwas wie "No Way", "Immer", "der einzige Weg" usw. schreibe, bedeutet es eigentlich "Auf keinen Fall, außer auf Reflection". In diesem speziellen Fall gibt es Object#instance_variable_set, zum Beispiel.

Mit anderen Worten: In Ruby sind Variablen immer privat, der einzige Weg, auf sie zuzugreifen, besteht darin, über eine Getter- und/oder Setter -Methode oder, wie sie in Ruby genannt werden, einen Attributleser und/oder einen Schriftsteller.

Jetzt schreibe ich weiter darüber Instanzvariablen, aber im Java -Beispiel haben wir Statische Felder, dh Klasse Variablen. Nun, im Gegensatz zu Java sind auch in Ruby Objekte. Sie sind Fälle der Class Klasse und so können sie wie jedes andere Objekt Instanzvariablen haben. In Ruby ist das Äquivalent einer Klassenvariablen wirklich nur eine Standard -Instanzvariable, die zu einem Objekt gehört, das zufällig eine Klasse ist.

(Es gibt auch Klassenhierarchievariablen, die mit einem Doppel beim Zeichen gekennzeichnet sind @@sigil. Die sind wirklich komisch, und Sie sollten sie wahrscheinlich nur ignorieren. Klassenhierarchievariablen werden in der gesamten Klassenhierarchie geteilt, dh der Klasse, zu der sie gehören, all seine Unterklassen und ihrer Unterklassen und deren Unterklassen ... und auch alle Instanzen all dieser Klassen. Eigentlich sind sie eher globale Variablen als Klassenvariablen. Sie sollten wirklich genannt werden $$var Anstatt von @@var, Da sie viel enger mit globalen Variablen zusammenhängen als Instanzvariablen. Sie sind nicht ganz nutzlos, aber nur sehr selten nützlich.)

Wir haben also den "Feld" -Teil (Java Field == Ruby Instance Variable) abgedeckt, wir haben die "öffentlichen" und "privaten" Teile abgedeckt (in Ruby, Instanzvariablen sind immer privat, wenn Sie sie öffentlich machen möchten. Verwenden Sie eine öffentliche Getter/Setter -Methode) und wir haben den "statischen" Teil (Java statisches Feld == Ruby Class Instance Variable) abgedeckt. Was ist mit dem "letzten" Teil?

In Java ist "Final" nur eine lustige Art der Rechtschreibung "const", die die Designer vermieden haben, weil die const Das Schlüsselwort in Sprachen wie C und C ++ ist subtil gebrochen und sie wollten Menschen nicht verwirren. Rubin tut Konstanten haben (mit einem Großbuchstaben angezeigt). Leider sind sie nicht wirklich konstant, denn der Versuch, sie zu ändern, während sie eine Warnung generiert, funktioniert tatsächlich. Sie sind also eher eine Konvention als eine Compiler-durchlässige Regel. Die wichtigere Einschränkung der Konstanten ist jedoch, dass sie immer öffentlich sind.

Konstanten sind also fast perfekt: Sie können nicht modifiziert werden (na ja, sie sollte nicht modifiziert werden), dh sie sind es final, sie gehören zu einer Klasse (oder einem Modul), dh sie sind es static. Aber sie sind immer public, so können sie leider nicht verwendet werden, um zu modellieren private static final Felder.

Und genau dies ist der Punkt, an dem das Denken über Probleme anstelle von Lösungen hereinkommt. Was wollen Sie? Sie wollen das sagen

  1. gehört zu einer Klasse,
  2. kann nur nicht geschrieben gelesen werden,
  3. wird nur einmal initialisiert und
  4. kann entweder privat oder öffentlich sein.

Sie können all das erreichen, aber auf eine ganz andere Weise als in Java:

  1. Klasseninstanzvariable
  2. Geben Sie keine Setter -Methode an, nur einen Getter
  3. Verwenden Sie Ruby's ||= Verbindungszuordnung, die nur einmal zugewiesen wird
  4. Getter -Methode

Das einzige, worüber Sie sich Sorgen machen müssen, ist, dass Sie nicht zuweisen @my_public_device Überall oder noch besser greifen Sie überhaupt nicht darauf zu. Verwenden Sie immer die Getter -Methode.

Ja das ist ein Loch in der Implementierung. Ruby wird oft als "Sprache des Erwachsenens" oder als "einverständliche Sprache der Erwachsenen" bezeichnet, was bedeutet, dass Sie sie nur in die Dokumentation versetzen und einfach darauf vertrauen, dass Ihre Kollegen gelernt haben, andere zu berühren Die Privaten der Menschen sind unhöflich ...


Ein total anders Der Ansatz zur Privatsphäre ist derjenige, der in funktionalen Sprachen verwendet wird: Verwenden Sie Verschluss. Verschlüsse sind Codeblöcke, die sich über ihre lexikalische Umgebung schließen, selbst nachdem diese lexikalische Umgebung den Umfang ausgeht. Diese Methode zur Implementierung des privaten Staates ist im Programm sehr beliebt, wurde jedoch kürzlich auch von Douglas Crockford et al. für JavaScript. Hier ist ein Beispiel in 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

Beachten Sie den subtilen, aber wichtigen Unterschied zu den Versionen oben in meiner Antwort: das Fehlen des Mangels @ Sigil. Hier schaffen wir lokal Variablen, nicht Beispiel Variablen. Sobald der Klassenkörper endet, fallen diese lokalen Variablen aus dem Zielfernrohr und können nie wieder zugegriffen werden. Nur Die beiden Blöcke, die die beiden Getter -Methoden definieren, haben immer noch Zugriff auf sie, da sie sich über den Klassenkörper schließen. Jetzt sind sie Ja wirklich Privatgelände und sie sind final, denn das einzige, was im gesamten Programm, das immer noch Zugriff auf sie hat Getter Methode.

Dies ist wahrscheinlich kein idiomatischer Rubin, aber für jeden mit einem Lisp- oder JavaScript -Hintergrund sollte es klar genug sein. Es ist auch sehr elegant.

Andere Tipps

Das nächste, was ich mir einer endgültigen Variablen vorstellen kann, ist, die variable als Instanzvariable eines Moduls in Frage zu stellen:

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)

Dies gibt aus:

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

Der Trick hier ist, dass Sie durch das Einkapseln des Geräts in ein Modul nur über dieses Modul auf das Gerät zugreifen können. Aus der Klasse Foo, Es gibt keine Möglichkeit zu ändern die Gerät, auf das Sie zugreifen, ohne direkt auf die zu handeln Device Klasse oder der FinalDevice Modul. Das freeze Rufen Sie an FinalDevice kann je nach Ihren Bedürfnissen angemessen sein oder nicht.

Wenn Sie einen öffentlichen und privaten Accessor machen möchten, können Sie ändern Foo so was:

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

In diesem Fall müssen Sie wahrscheinlich ändern FinalDevice::get_device Auch ein Streit einnehmen.

Update: @banister hat darauf hingewiesen @fin wie in FinalDevice ist in der Tat durch eine Instanz von zugänglich Foo. Ich hatte faul angenommen, da es nicht in der Standardtextausgabe von nicht war Foo#inspect, es war nicht drinnen Foo.

Sie können dies beheben, indem Sie expliziter machen @fin eine Instanzvariable der FinalDevice Modul:

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}")

Welche korrekt ausgibt:

fin was my_device, now it is my_device
class DeviceController
  MY_DEVICE = Device.get_device("mydevice")
end

Und ja, require 'device' wenn benötigt.

Obwohl nichts Sie davon abhalten wird, die Konstante woanders neu zu definieren, außer einer Warnung :)

Private Static in Ruby:

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

Öffentliche Statik in Ruby:

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

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

Ruby kann kein 'Final' haben :)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top