Eccoci all’ultimo articolo della serie rivolta ai distruttori di classe e più in generale a come fare per inserire un metodo da richiamare alla fine dell’utilizzo di una classe.
Nel precedente articolo abbiamo visto come fare per generare un evento in fase di distruzione dell’oggetto da parte del garbage collector e che richiami un metodo per svolgere le ultimissime operazioni.
Passiamo alla pratica e dopo i commenti:
class E #Creo una variabile di istanza, utile per i test attr_reader :var1 @@count=0 #Costruttore def initialize(var1) puts "Initializing #{self.object_id} var #{var1}" ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc) @var1=var1 @@count +=1 end #Metodo di servizio def body begin yield(self) rescue StandardError => err puts "Error: #{err}" ensure finalize end end #Metodo che fa il lavoro sporco def work puts 'Work' end private #Distruttore def finalize puts "Finalize #{@var1}" end def self.finalize(id) puts "*** Finalize id #{id} nr. #{@@count} ***" @@count -=1 #puts "Var1 #{ObjectSpace._id2ref(id).var1}" end end #Istanzio tre oggetti e li utilizzo ['aaa','bbb','ccc'].each do |str| E.new(str).body do |o| o.work #...e tutti gli altri metodi end end
L’output generato sarà questo:
D:\Marco\Progetti\Ruby\Batch\Test\Destructor>ruby D3.rb
Initializing 20806360 var aaa
Work
Finalize aaa
Initializing 20806220 var bbb
Work
Finalize bbb
Initializing 20806080 var ccc
Work
Finalize ccc
*** Finalize id 20806080 nr. 3 ***
*** Finalize id 20806220 nr. 2 ***
*** Finalize id 20806360 nr. 1 ***
Ci sono un pò di differenze rispetto al precedente articolo:
Questa volta il distruttore è richiamato direttamente nel costruttore perchè ho ipotizzato che servisse ad ogni oggetto, l’alternativa è definirlo come in un esempio precedente, solo per gli oggetti interessati, dopo l’istanziamento:
E.new('xxx').body do |o| ObjectSpace.define_finalizer(o, o.class.method(:finalize).to_proc) o.work end
Un altra differenza è che questa volta non si associa una procedura creata “al volo” ma un metodo di classe, nella definizione della classe è infatti presente un nuovo metodo: self.finalize
Ho inserito una variabile di classe, chiamata anche attributo statico, @@count. E’ in comune con tutte le istanze e nell’esempio serve per conteggiare gli oggetti della classe E. Nel distruttore viene mostrato il valore per informarci del numero rimanente di oggetti.
Questa volta non è necessario forzare l’esecuzione del garbage collector in quanto al termine dello script viene fatto in automatico generando l’evento che richiama il costruttore per ogni oggetto.
Ho cercato di semplificare il codice e cosi facendo ho omesso una finezza di ruby che devo però indicarla almeno come nota anche se non c’entra nulla coi distruttori. Se i metodi da richiamare sono molti si può utilizzare questa sintassi lasciando il codice molto leggibile:
E.new('xxx').body do |o| ObjectSpace.define_finalizer(o, o.class.method(:finalize).to_proc) ['work', 'hardwork', 'ecc'].each do |method_string| o.send(method_string) end end
Purtroppo non riesco ad utilizzare un metodo distruttore che sia univoco per istanza. Un altro tentativo poteva essere quello di puntare all’istanza tramite ObjectSpace che tramite il metodo _id2ref restituisce l’oggetto con id uguale a quello passato come parametro:
ObjectSpace._id2ref(id)
Sfortunatamente non funziona dall’interno del distruttore, probabilmente il garbage collector ha già distrutto l’istanza, questo è un peccato e spero esista un altro sistema che però non conosco…
Italiano
English