Die engste Ruby -Darstellung einer "privaten statischen Finale" und "öffentliche statische endgültige" Klassenvariable in Java?
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[])
{
...
}
}
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
- gehört zu einer Klasse,
- kann nur nicht geschrieben gelesen werden,
- wird nur einmal initialisiert und
- kann entweder privat oder öffentlich sein.
Sie können all das erreichen, aber auf eine ganz andere Weise als in Java:
- Klasseninstanzvariable
- Geben Sie keine Setter -Methode an, nur einen Getter
- Verwenden Sie Ruby's
||=
Verbindungszuordnung, die nur einmal zugewiesen wird - 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 :)