Rappresentazione Ruby più vicina di una variabile di classe "finale statica privata" e "finale statica pubblica" in Java?

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

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[])
  {
   ...
  }
}
È stato utile?

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

  1. appartiene ad una classe,
  2. può solo essere letto e non scritto,
  3. viene inizializzato solo una volta e
  4. può essere privato o pubblico.

Puoi ottenere tutto ciò, ma in un modo completamente diverso rispetto a Java:

  1. variabile di istanza della classe
  2. non fornire un metodo setter, solo un getter
  3. usa Ruby ||= assegnazione composta da assegnare una sola volta
  4. 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':)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top