Biorąc pod uwagę klasę, sprawdź, czy instancja ma metodę (Ruby)


227

W Ruby wiem, że mogę użyć, respond_to?aby sprawdzić, czy obiekt ma określoną metodę.

Ale biorąc pod uwagę klasę, jak mogę sprawdzić, czy instancja ma określoną metodę?

tj. coś podobnego

Foo.new.respond_to?(:bar)

Ale wydaje mi się, że musi być lepszy sposób niż utworzenie nowej instancji.

Odpowiedzi:


364

Nie wiem, dlaczego wszyscy sugerują, że powinieneś używać instance_methodsi include?kiedy method_defined?wykonuje swoją pracę.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

UWAGA

Jeśli przyjeżdżasz do Ruby z innego języka OO LUB uważasz, że method_definedoznacza to TYLKO metody, które wyraźnie zdefiniowałeś za pomocą:

def my_method
end

następnie przeczytaj to:

W Ruby właściwość (atrybut) w twoim modelu jest w zasadzie również metodą. Zwróci method_defined?również wartość true dla właściwości, a nie tylko metod.

Na przykład:

Biorąc pod uwagę instancję klasy, która ma atrybut String first_name:

<instance>.first_name.class #=> String

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

ponieważ first_namejest zarówno atrybutem, jak i metodą (oraz łańcuchem typu String).


9
zdefiniowano metodę? jest najlepszym rozwiązaniem, ponieważ metody instance_method zmieniły się między Ruby 1.8 a 1.9. 1.8 zwraca tablicę ciągów, a 1.9 zwraca tablicę symboli. zdefiniowano metodę? przyjmuje symbol zarówno w wersji 1.8, jak i 1.9.
Jason

2
Nie wspominając o tym, że: instance_methods utworzy nową tablicę przy każdym wywołaniu i że: include? będzie musiał przejść przez tablicę, aby powiedzieć. W przeciwieństwie do tego: method_defined? przeszukuje wewnętrznie tabele wyszukiwania metod (jedno wyszukiwanie skrótów w C) i informuje o tym bez tworzenia nowych obiektów.
Asher

4
Chociaż korzystanie z niego w ten sposób jest co najmniej jedną zaletą String.instance_methods(false).include? :freeze, fałszywy argument może dokładnie powiedzieć, czy freezemetoda jest zdefiniowana w klasie String, czy nie. W takim przypadku zwróci false, ponieważ freezemetoda jest dziedziczona z modułu jądra przez String, ale String.method_defined? :freezezawsze zwróci true, co niewiele może pomóc w takim celu.
ugoa

1
@ Zgubiłeś mylące metody w klasie z metodami dostępnymi w instancji. method_defined?musi być użyty w klasie (np. Array), ale próbujesz użyć go w instancjach (np. [])
horseyguy

@banister Absolutnie poprawne. Dziękuję za wyjaśnienie, jego przeczytanie ma sens. :) Usunąłem swój komentarz, aby się nie mylić.
Zmora

45

Możesz użyć method_defined?w następujący sposób:

String.method_defined? :upcase # => true

Znacznie łatwiejsze, przenośniejsze i wydajniejsze niż instance_methods.include?sugerują to wszyscy inni.

Pamiętaj, że nie będziesz wiedział, czy klasa reaguje dynamicznie na niektóre wywołania method_missing, na przykład poprzez redefiniowanie respond_to?lub od Ruby 1.9.2 poprzez zdefiniowanie respond_to_missing?.


2
Pamiętaj, że jeśli masz instancję w ręku i nie wiesz, że to klasa, możesz to zrobićinstance.class.method_defined? :upcase
jaydel,

25

W rzeczywistości nie działa to zarówno dla obiektów, jak i klas.

To robi:

class TestClass
  def methodName
  end
end

Tak więc przy podanej odpowiedzi działa to:

TestClass.method_defined? :methodName # => TRUE

Ale to NIE działa:

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

Używam tego zarówno dla klas, jak i obiektów:

Klasy:

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

Obiekty:

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

3
W przypadku instancji możesz również użyć `instance.class.method_defined? '
Luksurious

To zależy od twojej wersji rubinu. Powyższa metoda działa dla wszystkich wersji, w tym 1.8.7.
Alex V

10

Odpowiedź na „ Biorąc pod uwagę klasę, czy instancja ma metodę (Ruby) ” jest lepsza. Najwyraźniej Ruby ma to wbudowane i jakoś mi tego brakowało. Moja odpowiedź pozostawia się do odniesienia, niezależnie od tego.

Klasy Ruby odpowiadają na metody instance_methodsi public_instance_methods. W Ruby 1.8 pierwszy wyświetla wszystkie nazwy metod instancji w tablicy ciągów, a drugi ogranicza je do metod publicznych. Drugie zachowanie jest najbardziej pożądane, ponieważ respond_to?domyślnie ogranicza się również do metod publicznych.

Foo.public_instance_methods.include?('bar')

Jednak w Ruby 1.9 metody te zwracają tablice symboli.

Foo.public_instance_methods.include?(:bar)

Jeśli planujesz to robić często, możesz chcieć rozszerzyć Moduleo metodę skrótów. (Może się to wydawać dziwne, aby przypisać to Modulezamiast Class, ale ponieważ tam właśnie instance_methodsżyją metody, najlepiej trzymać się tego wzorca.)

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

Jeśli chcesz obsługiwać zarówno Ruby 1.8, jak i Ruby 1.9, byłoby to wygodne miejsce na dodanie logiki do wyszukiwania zarówno ciągów znaków, jak i symboli.


1
method_defined?jest lepiej :)
horseyguy

@banister - och, ja i ja jakoś to przeoczyłem! xD Rzucił +1 za odpowiedź @ Marc i dodał link, aby upewnić się, że nie zostanie pominięty :)
Matchu


3

Nie jestem pewien, czy to najlepszy sposób, ale zawsze możesz to zrobić:

Foo.instance_methods.include? 'bar'


3

Jeśli sprawdzasz, czy obiekt może odpowiedzieć na szereg metod, możesz zrobić coś takiego:

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

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

methods & something.methodsdołączy do dwóch tablic na ich elementów wspólnych / dopasowanie. coś.metody obejmują wszystkie sprawdzane metody, będą równe metodom. Na przykład:

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

więc

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

W tej sytuacji chciałbyś użyć symboli, ponieważ gdy wywołasz .methods, zwróci tablicę symboli, a jeśli użyjesz ["my", "methods"], zwróci false.


2

klass.instance_methods.include :method_namelub "method_name", w zależności od wersji Ruby, myślę.


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] 

Mam nadzieję, że to ci pomoże!


1

W moim przypadku praca z Ruby 2.5.3 działały idealnie następujące zdania:

value = "hello world"

value.methods.include? :upcase

Zwróci wartość logiczną true lub false.


1

Chociaż respond_to?zwróci prawdę tylko w przypadku metod publicznych, sprawdzanie „definicji metody” w klasie może również dotyczyć metod prywatnych.

W Ruby v2.0 + można sprawdzać zarówno zestawy publiczne, jak i prywatne

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.