Rappresentazione Ruby più vicina di una variabile di classe "finale statica privata" e "finale statica pubblica" in Java?
Domanda
Considerando il codice Java riportato di seguito, qual è la cosa più vicina che potresti rappresentare questi due? static final
variabili in una classe Ruby?Ed è possibile in Ruby distinguere tra private static
E public static
variabili come 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[])
{
...
}
}
Soluzione
In realtà non esiste un costrutto equivalente in Ruby.
Tuttavia, sembra che tu stia commettendo uno dei classici errori di porting:hai un soluzione nella lingua A e prova a tradurlo nella lingua B, quando quello che dovresti fare veramente è capire il problema e poi capire come risolverlo nella lingua B.
Non posso davvero essere sicuro di quale sia il problema che stai cercando di risolvere da quel piccolo codenippet, ma eccolo qui uno possibile idea su come implementarlo in 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
Eccone un altro:
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 differenza è che il primo esempio è pigro, inizializza la variabile di istanza solo quando viene chiamato per la prima volta il lettore di attributi corrispondente.La seconda li inizializza non appena viene eseguito il corpo della classe, anche se non sono mai necessari, proprio come fa la versione Java.)
Esaminiamo alcuni dei concetti qui.
In Ruby, come in ogni altro linguaggio orientato agli oggetti "proprio" (per varie definizioni di "proprio"), lo stato (variabili di istanza, campi, proprietà, slot, attributi, come vuoi chiamarli) è Sempre privato.C'è non c'è modo per accedervi dall'esterno.L'unico modo per comunicare con un oggetto è inviargli messaggi.
[Nota:Ogni volta che scrivo qualcosa come "assolutamente no", "sempre", "l'unico modo" ecc., in realtà no significa "assolutamente no, tranne che per la riflessione".In questo caso particolare, c'è Object#instance_variable_set
, Per esempio.]
In altre parole:in Ruby, le variabili sono sempre private, l'unico modo per accedervi è tramite un metodo getter e/o setter o, come vengono chiamati in Ruby, un lettore e/o scrittore di attributi.
Ora continuo a scrivere di variabili di istanza, ma nell'esempio Java abbiamo campi statici, cioè. classe variabili.Bene, in Ruby, a differenza di Java, anche le classi sono oggetti.Sono esempi di Class
class e quindi, proprio come qualsiasi altro oggetto, possono avere variabili di istanza.Quindi, in Ruby, l'equivalente di una variabile di classe è in realtà solo una variabile di istanza standard che appartiene a un oggetto che sembra essere una classe.
(Esistono anche variabili della gerarchia di classi, indicate con una doppia chiocciola @@sigil
.Sono davvero strani e probabilmente dovresti semplicemente ignorarli.Le variabili della gerarchia di classi sono condivise nell'intera gerarchia di classi, ad es.la classe a cui appartengono, tutte le sue sottoclassi e le loro sottoclassi e le loro sottoclassi ...e anche tutte le istanze di tutte quelle classi.In realtà, sono più simili a variabili globali che a variabili di classe.Dovrebbero davvero essere chiamati $$var
invece di @@var
, poiché sono molto più strettamente correlati alle variabili globali rispetto alle variabili di istanza.Non sono del tutto inutili ma solo molto raramente utili.)
Quindi, abbiamo trattato la parte "campo" (campo Java == variabile di istanza Ruby), abbiamo trattato le parti "pubblica" e "privata" (in Ruby, le variabili di istanza sono sempre private, se vuoi renderle pubbliche, utilizzare un metodo getter/setter pubblico) e abbiamo trattato la parte "statica" (campo statico Java == variabile di istanza della classe Ruby).E la parte "finale"?
In Java, "final" è solo un modo divertente di scrivere "const", che i progettisti hanno evitato perché il file const
La parola chiave in linguaggi come C e C++ è leggermente interrotta e non volevano confondere le persone.Rubino fa hanno costanti (indicate iniziando con una lettera maiuscola).Purtroppo non sono proprio costanti, perché provare a modificarli, generando al contempo un avviso, effettivamente funziona.Quindi, sono più una convenzione che una regola applicata dal compilatore.Tuttavia, la restrizione più importante delle costanti è che sono sempre pubbliche.
Quindi, le costanti sono quasi perfette:non possono essere modificati (beh, loro non dovrebbe essere modificato), vale a diresono final
, appartengono a una classe (o modulo), cioèsono static
.Ma lo sono sempre public
, quindi purtroppo non possono essere utilizzati per modellare private static final
campi.
Ed è proprio qui che entra in gioco pensare ai problemi invece che alle soluzioni.Cos'è che vuoi?Vuoi dirlo
- appartiene ad una classe,
- può solo essere letto e non scritto,
- viene inizializzato solo una volta e
- può essere privato o pubblico.
Puoi ottenere tutto ciò, ma in un modo completamente diverso rispetto a Java:
- variabile di istanza della classe
- non fornire un metodo setter, solo un getter
- usa Ruby
||=
assegnazione composta da assegnare una sola volta - metodo getter
L'unica cosa di cui devi preoccuparti è di non assegnare @my_public_device
ovunque o, meglio ancora, non accedervi affatto.Usa sempre il metodo getter.
Si Questo È un buco nell'implementazione.Ruby è spesso definito un "linguaggio per adulti" o un "linguaggio per adulti consenzienti", il che significa che invece di chiedere al compilatore di imporre certe cose, le inserisci semplicemente nella documentazione e ti fidi semplicemente che i tuoi colleghi sviluppatori abbiano imparato che toccando altri i intimi delle persone sono scortesi...
Un totale diverso L’approccio alla privacy è quello utilizzato nei linguaggi funzionali:utilizzare le chiusure.Le chiusure sono blocchi di codice che chiudono il loro ambiente lessicale, anche dopo che tale ambiente lessicale è uscito dall'ambito.Questo metodo di implementazione dello stato privato è molto popolare in Scheme, ma è stato recentemente reso popolare anche da Douglas Crockford et al.per JavaScript.Ecco un esempio 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
Nota la sottile ma importante differenza rispetto alle versioni nella parte superiore della mia risposta:la mancanza del @
sigillo.Qui stiamo creando Locale variabili, no esempio variabili.Non appena il corpo della classe termina, quelle variabili locali escono dall'ambito e non sarà mai più possibile accedervi. Soltanto i due blocchi che definiscono i due metodi getter hanno comunque accesso ad essi, perché si chiudono sul corpo della classe.Ora lo sono Veramente privato E sono final
, perché l'unica cosa nell'intero programma che ha ancora accesso ad essi è un file pure getter metodo.
Questo probabilmente non è Ruby idiomatico, ma per chiunque abbia un background Lisp o JavaScript dovrebbe essere abbastanza chiaro.È anche molto elegante.
Altri suggerimenti
La cosa più vicina che posso pensare ad una variabile finale è quello di mettere la variabile in questione come una variabile di istanza di un modulo:
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)
Questa Uscite:
#<Foo:0xb78a74f8 @my_instance_var=1>
true
Il trucco è che incapsulando il dispositivo in un modulo, è possibile accedere solo il dispositivo tramite quel modulo. Dal Foo
di classe, non c'è modo di modificare che dispositivo che si sta accedendo, senza agire direttamente dalla classe Device
o il modulo FinalDevice
. La chiamata in freeze
FinalDevice
può o non può essere appropriato, a seconda delle esigenze.
Se si vuole fare una funzione di accesso pubblico e privato, è possibile modificare Foo
in questo modo:
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 questo caso avrete probabilmente bisogno di modificare FinalDevice::get_device
a prendere un argomento così.
Aggiornamento: @banister ha sottolineato che @fin
come dichiarato nel FinalDevice
è infatti accessibile da un'istanza di Foo
. Avevo supposto che pigramente dato che non era nel testo di output predefinito Foo#inspect
, non è stato Foo
dentro.
È possibile rimediare a questo da più esplicitamente facendo @fin
una variabile del modulo FinalDevice
esempio:
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}")
Quali uscite correttamente:
fin was my_device, now it is my_device
class DeviceController
MY_DEVICE = Device.get_device("mydevice")
end
E sì, require 'device'
se necessario.
Anche se nulla vi vieta di ridefinizione costante da qualche altra parte, ad eccezione di un avvertimento:)
private static in Ruby:
class DeviceController
@@my_device = Device.get_device("mydevice")
end
public static in Ruby:
class DeviceController
def self.my_device; @@my_device; end
@@my_device = Device.get_device("mydevice")
end
Rubino non può avere 'finale':)