Data una classe, vedere se l'istanza ha metodo (Ruby)


227

So in Ruby che posso usare respond_to?per verificare se un oggetto ha un certo metodo.

Ma, data la classe, come posso verificare se l'istanza ha un certo metodo?

cioè qualcosa di simile

Foo.new.respond_to?(:bar)

Ma penso che ci debba essere un modo migliore di creare un'istanza di una nuova istanza.

Risposte:


364

Non so perché tutti suggeriscano che dovresti usare instance_methodse include?quando method_defined?fa il lavoro.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

NOTA

Nel caso in cui venissi a Ruby da un'altra lingua OO O pensi che ciò method_definedsignifichi SOLO metodi che hai definito esplicitamente con:

def my_method
end

quindi leggi questo:

In Ruby, una proprietà (attributo) sul tuo modello è sostanzialmente anche un metodo. Quindi method_defined?restituirà anche true per le proprietà, non solo per i metodi.

Per esempio:

Data un'istanza di una classe che ha un attributo String first_name:

<instance>.first_name.class #=> String

<instance>.class.method_defined?(:first_name) #=> true

poiché first_nameè sia un attributo che un metodo (e una stringa di tipo String).


9
method_defined? è la soluzione migliore perché istanza_metodi cambiata tra Ruby 1.8 e 1.9. 1.8 restituisce una matrice di stringhe e 1.9 restituisce una matrice di simboli. method_defined? prende un simbolo in 1.8 e 1.9.
Jason,

2
Per non parlare del fatto che: instance_methods creerà un nuovo array ogni chiamata e che: include? dovrà quindi camminare sull'array per dirlo. Al contrario,: ​​method_defined? interrogherà internamente le tabelle di ricerca del metodo (una ricerca hash che si trova in C) e ti farà sapere senza creare nuovi oggetti.
Asher,

4
Sebbene ci sia almeno un vantaggio quando lo usi in questo modo String.instance_methods(false).include? :freeze, il falso argomento può dire esattamente se il freezemetodo è definito o meno nella classe String. In questo caso restituirà false perché il freezemetodo è ereditato dal modulo Kernel da String, ma String.method_defined? :freezerestituirà sempre true, il che non può essere di grande aiuto con tale scopo.
ugoa,

1
@Bane stai confondendo i metodi sulla classe con i metodi disponibili sull'istanza. method_defined?deve essere utilizzato sulla classe (ad es. Array), ma si sta tentando di utilizzarlo su istanze (ad es. [])
horseyguy

@banister Assolutamente corretto. Grazie per il chiarimento, ha perfettamente senso leggerlo. :) Ho rimosso il mio commento per non confondere.
Bane,

45

Puoi usare method_defined?come segue:

String.method_defined? :upcase # => true

Molto più facile, portatile ed efficiente di quanto instance_methods.include?tutti gli altri sembrano suggerire.

Tieni presente che non saprai se una classe risponde in modo dinamico ad alcune chiamate con method_missing, ad esempio ridefinendo respond_to?, o da Ruby 1.9.2 definendo respond_to_missing?.


2
Nota che se hai un'istanza in mano e non sai che è di classe, puoi farloinstance.class.method_defined? :upcase
jaydel,

25

In realtà questo non funziona per oggetti e classi.

Questo fa:

class TestClass
  def methodName
  end
end

Quindi, con la risposta data, questo funziona:

TestClass.method_defined? :methodName # => TRUE

Ma questo non funziona:

t = TestClass.new
t.method_defined? : methodName  # => ERROR!

Quindi lo uso sia per le classi che per gli oggetti:

Classi:

TestClass.methods.include? 'methodName'  # => TRUE

Oggetti:

t = TestClass.new
t.methods.include? 'methodName'  # => TRUE

3
Ad esempio, puoi anche usare `instance.class.method_defined? '
Luksurious

Dipende dalla tua versione di ruby. Questo metodo sopra funziona per tutte le versioni incluso 1.8.7.
Alex V,

10

La risposta a " Data una classe, vedi se l'istanza ha metodo (Ruby) " è migliore. Apparentemente Ruby ha questo built-in, e in qualche modo mi è mancato. La mia risposta è lasciata come riferimento, a prescindere.

Le classi di Ruby rispondono ai metodi instance_methodse public_instance_methods. In Ruby 1.8, il primo elenca tutti i nomi dei metodi di istanza in una matrice di stringhe e il secondo lo limita ai metodi pubblici. Il secondo comportamento è quello che probabilmente vorresti, dal momento che si respond_to?limita ai metodi pubblici per impostazione predefinita.

Foo.public_instance_methods.include?('bar')

In Ruby 1.9, tuttavia, questi metodi restituiscono matrici di simboli.

Foo.public_instance_methods.include?(:bar)

Se hai intenzione di farlo spesso, potresti voler estendere Moduleper includere un metodo di scelta rapida. (Può sembrare strano assegnare questo Moduleinvece di Class, ma poiché è lì che vivono i instance_methodsmetodi, è meglio rimanere in linea con quel modello.)

class Module
  def instance_respond_to?(method_name)
    public_instance_methods.include?(method_name)
  end
end

Se si desidera supportare sia Ruby 1.8 che Ruby 1.9, sarebbe un posto comodo per aggiungere la logica per cercare anche stringhe e simboli.


1
method_defined?è meglio :)
Horseyguy,

@banister - oh, mio, e in qualche modo me lo sono perso! xD Ha lanciato un +1 dietro la risposta di
@Marc


3

Non sono sicuro se questo è il modo migliore, ma potresti sempre farlo:

Foo.instance_methods.include? 'bar'


3

Se stai verificando se un oggetto è in grado di rispondere a una serie di metodi, potresti fare qualcosa del tipo:

methods = [:valid?, :chase, :test]

def has_methods?(something, methods)
  methods & something.methods == methods
end

la methods & something.methodssi uniranno due matrici sui loro elementi comuni / corrispondenza. something.methods include tutti i metodi che stai cercando, sarà uguale ai metodi. Per esempio:

[1,2] & [1,2,3,4,5]
==> [1,2]

così

[1,2] & [1,2,3,4,5] == [1,2]
==> true

In questa situazione, vorresti usare i simboli, perché quando chiami .methods, restituisce un array di simboli e se lo usi ["my", "methods"], restituisce false.


2

klass.instance_methods.include :method_nameo "method_name", a seconda della versione di Ruby, penso.


2
class Foo
 def self.fclass_method
 end
 def finstance_method
 end
end

foo_obj = Foo.new
foo_obj.class.methods(false)
=> [:fclass_method] 

foo_obj.class.instance_methods(false)
=> [:fclass_method] 

Spero che questo ti aiuti!


1

Nel mio caso lavorando con ruby ​​2.5.3 le seguenti frasi hanno funzionato perfettamente:

value = "hello world"

value.methods.include? :upcase

Restituirà un valore booleano vero o falso.


1

Mentre respond_to?restituirà true solo per i metodi pubblici, il controllo della "definizione del metodo" su una classe può anche riguardare metodi privati.

Su Ruby v2.0 + è possibile controllare sia i set pubblici che quelli privati

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.