รับคลาสดูว่าอินสแตนซ์มีเมธอด (Ruby) หรือไม่


227

ฉันรู้ใน Ruby ว่าฉันสามารถใช้respond_to?เพื่อตรวจสอบว่าวัตถุมีวิธีการบางอย่าง

แต่ให้ชั้นเรียนฉันจะตรวจสอบว่าอินสแตนซ์มีวิธีการบางอย่างได้อย่างไร

นั่นคือสิ่งที่ชอบ

Foo.new.respond_to?(:bar)

แต่ฉันรู้สึกว่ามีวิธีที่ดีกว่าการสร้างอินสแตนซ์ใหม่

คำตอบ:


364

ฉันไม่รู้ว่าทำไมทุกคนแนะนำว่าคุณควรใช้instance_methodsและทำงานinclude?เมื่อmethod_defined?ไหร่

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

บันทึก

ในกรณีที่คุณมาทับทิมจากภาษา OO อื่นหรือคุณคิดว่าmethod_definedหมายถึงวิธีการเดียวที่คุณกำหนดไว้อย่างชัดเจนด้วย:

def my_method
end

แล้วอ่านสิ่งนี้:

ใน Ruby คุณสมบัติ (attribute) ในแบบจำลองของคุณนั้นเป็นวิธีการเช่นกัน ดังนั้นmethod_defined?จะส่งคืนจริงสำหรับคุณสมบัติไม่ใช่เฉพาะเมธอด

ตัวอย่างเช่น:

กำหนดอินสแตนซ์ของคลาสที่มีแอตทริบิวต์ String first_name:

<instance>.first_name.class #=> String

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

เนื่องจากfirst_nameเป็นทั้งแอตทริบิวต์และเมธอด (และสตริงประเภท String)


9
method_defined? เป็นทางออกที่ดีที่สุดเนื่องจาก instance_methods เปลี่ยนไประหว่าง Ruby 1.8 และ 1.9 1.8 ส่งคืนอาร์เรย์ของสตริงและ 1.9 ส่งคืนอาร์เรย์ของสัญลักษณ์ method_defined? ใช้สัญลักษณ์ทั้ง 1.8 และ 1.9
Jason

2
ไม่พูดถึง: instance_methods จะสร้างอาร์เรย์ใหม่ทุกการโทรและที่: รวม? จะต้องเดินแถวลำดับต่อไปเพื่อบอก ในทางตรงกันข้าม: method_defined? จะสอบถามตารางการค้นหาวิธีการภายใน (หนึ่งแฮชการค้นหาที่อยู่ใน C) และแจ้งให้คุณทราบโดยไม่ต้องสร้างวัตถุใหม่ใด ๆ
Asher

4
ในขณะที่มีอย่างน้อยหนึ่งข้อได้เปรียบเมื่อคุณใช้มันเช่นนี้String.instance_methods(false).include? :freezeที่เป็นเท็จอาร์กิวเมนต์สามารถบอกได้ว่าไม่ว่าจะเป็นfreezeวิธีการที่กำหนดไว้ในระดับ String หรือไม่ ในกรณีนี้มันจะคืนค่าเท็จเนื่องจากfreezeเมธอดนั้นสืบทอดมาจากโมดูลเคอร์เนลด้วย String แต่String.method_defined? :freezeจะคืนค่าจริงเสมอซึ่งไม่สามารถช่วยอะไรได้มากกับวัตถุประสงค์ดังกล่าว
ugoa

1
@Bane คุณกำลังสับสนวิธีการในชั้นเรียนด้วยวิธีการที่มีอยู่ในอินสแตนซ์ method_defined?ต้องใช้กับคลาส (เช่นArray) แต่คุณพยายามใช้กับอินสแตนซ์ (เช่น[])
horseyguy

@banister ถูกต้องแน่นอน ขอบคุณสำหรับความกระจ่างมันทำให้รู้สึกที่สมบูรณ์แบบในการอ่านมัน :) ฉันได้ลบความคิดเห็นของฉันเพื่อไม่ให้สับสน
สารพิษ

45

คุณสามารถใช้method_defined?ดังนี้

String.method_defined? :upcase # => true

ง่ายกว่าพกพาและมีประสิทธิภาพมากกว่าinstance_methods.include?คนอื่น ๆ ดูเหมือนว่าจะแนะนำ

เก็บไว้ในใจว่าคุณจะไม่ทราบว่าระดับตอบสนองแบบไดนามิกเพื่อบางสายด้วยmethod_missingเช่นโดยการกําหนดrespond_to?หรือตั้งแต่ทับทิม 1.9.2 respond_to_missing?ด้วยการกำหนด


2
โปรดทราบว่าหากคุณมีตัวอย่างในมือและคุณไม่ทราบว่าเป็นคลาสคุณสามารถทำได้instance.class.method_defined? :upcase
jaydel

25

อันที่จริงมันใช้ไม่ได้กับทั้ง Objects และ Classes

สิ่งนี้:

class TestClass
  def methodName
  end
end

ดังนั้นด้วยคำตอบที่ได้รับงานนี้:

TestClass.method_defined? :methodName # => TRUE

แต่นี่ไม่ได้ผล:

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

ดังนั้นฉันจึงใช้สิ่งนี้สำหรับทั้งคลาสและวัตถุ:

ชั้นเรียน:

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

วัตถุที่:

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

3
สำหรับอินสแตนซ์คุณสามารถใช้ `instance.class.method_defined? '
Luksurious

ขึ้นอยู่กับรุ่นของทับทิม วิธีการข้างต้นนี้ใช้ได้กับทุกรุ่นรวมถึง 1.8.7
อเล็กซ์ V

10

คำตอบของ " ให้ชั้นเรียนดูว่าอินสแตนซ์มีเมธอด (Ruby) " ดีกว่าหรือไม่ เห็นได้ชัดว่า Ruby มีตัวนี้อยู่และฉันก็พลาดไป คำตอบของฉันถูกทิ้งไว้สำหรับการอ้างอิงโดยไม่คำนึงถึง

เรียนทับทิมตอบสนองต่อวิธีการและinstance_methods public_instance_methodsใน Ruby 1.8 รายการแรกจะแสดงชื่อเมธอดอินสแตนซ์ทั้งหมดในอาร์เรย์ของสตริงและอันดับที่สอง จำกัด เป็นวิธีสาธารณะ พฤติกรรมที่สองคือสิ่งที่คุณต้องการมากที่สุดเนื่องจากrespond_to?จำกัด ตัวเองเป็นวิธีสาธารณะโดยค่าเริ่มต้นเช่นกัน

Foo.public_instance_methods.include?('bar')

อย่างไรก็ตามใน Ruby 1.9 วิธีการเหล่านั้นจะคืนค่าอาร์เรย์ของสัญลักษณ์

Foo.public_instance_methods.include?(:bar)

หากคุณวางแผนที่จะทำเช่นนี้บ่อยครั้งคุณอาจต้องการขยายModuleเพื่อรวมวิธีการทางลัด (อาจดูเหมือนแปลกที่จะกำหนดสิ่งนี้ให้ModuleแทนClassแต่เนื่องจากเป็นที่ที่instance_methodsวิธีการอาศัยอยู่จึงเป็นสิ่งที่ดีที่สุดเพื่อให้สอดคล้องกับรูปแบบนั้น)

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

หากคุณต้องการสนับสนุนทั้ง Ruby 1.8 และ Ruby 1.9 นั่นจะเป็นสถานที่ที่สะดวกในการเพิ่มตรรกะเพื่อค้นหาทั้งสตริงและสัญลักษณ์เช่นกัน


1
method_defined?ดีกว่า :)
horseyguy

@ แบนเตอร์ - โอ้ฉันและฉันคิดถึงมันอย่างใด! xD +1 คำตอบของ @ Marc แล้วเพิ่มลิงก์เพื่อให้แน่ใจว่ามันจะไม่พลาด :)
Matchu


3

ไม่แน่ใจว่านี่เป็นวิธีที่ดีที่สุดหรือไม่ แต่คุณสามารถทำสิ่งนี้ได้ตลอดเวลา:

Foo.instance_methods.include? 'bar'

3

ฉันคิดว่ามีบางอย่างผิดปกติmethod_defined?ใน Rails มันอาจจะไม่สอดคล้องกันหรือบางอย่างดังนั้นถ้าคุณใช้ Rails มันจะดีกว่าถ้าใช้อะไรattribute_method?(attribute)มันอาจจะไม่สอดคล้องหรืออะไรดังนั้นหากคุณใช้ทางรถไฟดีกว่าที่จะใช้อะไรบางอย่างจาก

" การทดสอบ method_defined? บนคลาส ActiveRecord ไม่ทำงานจนกว่าจะเริ่มอินสแตนซ์ " เป็นคำถามเกี่ยวกับความไม่สอดคล้องกัน


3

หากคุณกำลังตรวจสอบเพื่อดูว่าวัตถุสามารถตอบสนองต่อวิธีการต่างๆได้หรือไม่คุณสามารถทำสิ่งต่อไปนี้

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

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

methods & something.methodsจะเข้าร่วมในสองอาร์เรย์ทั่วไป / องค์ประกอบการจับคู่ของพวกเขา บางสิ่งบางอย่างวิธีรวมถึงวิธีการทั้งหมดที่คุณกำลังตรวจสอบมันจะเป็นวิธีการที่เท่าเทียมกัน ตัวอย่างเช่น:

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

ดังนั้น

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

ในสถานการณ์เช่นนี้คุณต้องการใช้สัญลักษณ์เพราะเมื่อคุณเรียก. วิธีมันจะส่งกลับอาร์เรย์ของสัญลักษณ์และถ้าคุณใช้["my", "methods"]มันจะส่งกลับเท็จ



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] 

หวังว่านี่จะช่วยคุณได้!


1

ในกรณีของฉันทำงานกับทับทิม 2.5.3 ประโยคต่อไปนี้ทำงานได้อย่างสมบูรณ์แบบ:

value = "hello world"

value.methods.include? :upcase

มันจะคืนค่าบูลีนจริงหรือเท็จ


1

ในขณะที่respond_to?จะคืนค่าเป็นจริงสำหรับวิธีสาธารณะเท่านั้นการตรวจหา "การกำหนดวิธีการ" ในชั้นเรียนอาจเกี่ยวข้องกับวิธีการส่วนตัว

บน Ruby v2.0 + การตรวจสอบทั้งชุดสาธารณะและส่วนตัวสามารถทำได้ด้วย

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.