ถือกระเป๋าถือขึ้นเครื่องเพราะวันนี้เราจะเดินทางไปจนถึงห่วงโซ่บรรพบุรุษ เราจะติดตามการเรียกเมธอดและดูว่ามันจะขึ้นกับเชนได้อย่างไร และดูว่าเกิดอะไรขึ้นหากเมธอดนั้นหายไป และเพราะว่าเราชอบเล่นกับไฟ เราจะไม่หยุดเพียงแค่นั้น แต่ไปต่อที่ เล่นกับไฟ แทนที่ BasicObject#method_missing
. หากคุณให้ความสนใจ เราอาจใช้ตัวอย่างนี้ในเชิงปฏิบัติด้วย ไม่มีการค้ำประกันแม้ว่า ไปกันเถอะ!
ห่วงโซ่บรรพบุรุษ
เริ่มต้นด้วยกฎพื้นฐานของกลุ่มบรรพบุรุษใน Ruby:
- Ruby รองรับการสืบทอดเดียวเท่านั้น
- นอกจากนี้ยังช่วยให้วัตถุรวมชุดของโมดูลได้
ใน Ruby สายบรรพบุรุษประกอบด้วยการข้ามผ่านของคลาสและโมดูลที่สืบทอดมาทั้งหมดสำหรับคลาสที่กำหนด
มาดูตัวอย่างเพื่อแสดงให้เห็นว่าห่วงโซ่บรรพบุรุษได้รับการจัดการใน Ruby อย่างไร
module Auth end
module Session end
module Iterable end
class Collection
prepend Iterable
end
class Users < Collection
prepend Session
include Auth
end
p Users.ancestors
ผลิต:
[
Session, Users, Auth, # Users
Iterable, Collection, # Collection
Object, Kernel, BasicObject # Ruby Object Model
]
อันดับแรก เราเรียก ancestors
วิธีการเรียนเพื่อเข้าถึงสายบรรพบุรุษของคลาสที่กำหนด
เราจะเห็นว่ามีการเรียก Users.ancestors
ส่งคืนอาร์เรย์ของคลาสและโมดูลที่ประกอบด้วย:
- โมดูลที่นำหน้าของ
Users
- The
Users
คลาส - The
Users
โมดูลที่รวมคลาส - The
Collection
โมดูลเสริมของคลาส — เป็นพาเรนต์โดยตรงของผู้ใช้ - The
Collection
คลาส - The
Collection
โมดูลที่รวมคลาส — ไม่มี - The
Object
class — การสืบทอดเริ่มต้นของคลาสใดๆ - The
Kernel
module — รวมอยู่ในObject
และถือวิธีการหลัก - The
BasicObject
class — คลาสรูทใน Ruby
ดังนั้น ลำดับการแสดงของคลาสหรือโมดูลที่สำรวจจะเป็นดังนี้:
- โมดูลเสริม
- คลาสหรือโมดูล
- รวมโมดูล
กลุ่มบรรพบุรุษส่วนใหญ่จะผ่านโดย Ruby เมื่อมีการเรียกใช้เมธอดบนวัตถุหรือคลาส
เส้นทางการค้นหาเมธอด
เมื่อมีการส่งข้อความ Ruby จะข้ามกลุ่มบรรพบุรุษของผู้รับข้อความและตรวจสอบว่ามีข้อความใดบ้างที่ตอบสนองต่อข้อความที่กำหนด
หากคลาสหรือโมดูลที่กำหนดของสายบรรพบุรุษตอบสนองต่อข้อความ เมธอดที่เกี่ยวข้องกับข้อความนี้จะถูกดำเนินการและการข้ามผ่านของสายบรรพบุรุษจะหยุดลง
class Collection < Array
end
Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject]
collection = Collection.new([:a, :b, :c])
collection.each_with_index # => :a
ที่นี่ collection.each_with_index
ได้รับข้อความโดยโมดูลที่นับได้ จากนั้น Enumerable#each_with_index
เมธอดถูกเรียกสำหรับข้อความนี้
ที่นี่เมื่อ collection.each_with_index
ถูกเรียก Ruby ตรวจสอบว่า:
- คอลเล็กชันตอบสนองต่อ
each_with_index message
=> ไม่ - Array ตอบสนองต่อ
each_with_index message
=> ไม่ - นับได้ตอบสนองต่อข้อความ
each_with_index message
=> ใช่
ดังนั้น จากที่นี่ Ruby จะหยุดการข้ามผ่านสายของบรรพบุรุษและเรียกใช้เมธอดที่เกี่ยวข้องกับข้อความนี้ ในกรณีของเรา Enumerable#each_with_index
วิธีการ
ใน Ruby กลไกนี้เรียกว่า Method Lookup Path .
ตอนนี้ จะเกิดอะไรขึ้นถ้าไม่มีคลาสและโมดูลที่ประกอบเป็นสายบรรพบุรุษของผู้รับที่ระบุตอบสนองต่อข้อความ
BasicObject#method_missing
พอเล่นดี! มาทำลายสิ่งต่าง ๆ สไตล์นักพัฒนา:โดยการโยนข้อยกเว้น เราจะใช้ Collection
คลาสและเรียกใช้เมธอดที่ไม่รู้จักในอินสแตนซ์ตัวใดตัวหนึ่ง
class Collection
end
c = Collection.new
c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>
ที่นี่ Collection
คลาสไม่ได้ใช้ search
กระบวนการ. ดังนั้น NoMethodError
ถูกยกขึ้น แต่การทำให้เกิดข้อผิดพลาดนี้มาจากไหน?
เกิดข้อผิดพลาดใน BasicObject#method_missing
กระบวนการ. เมธอดนี้ถูกเรียกเมื่อ Method Lookup Path จบลงด้วยการไม่พบวิธีการใดๆ ที่สอดคล้องกับข้อความที่กำหนด
โอเค... แต่วิธีนี้จะเพิ่มเฉพาะ NoMethodError
. ดังนั้นจึงเป็นการดีที่จะสามารถแทนที่เมธอดในบริบทของ Collection
. ของเรา ชั้นเรียน
การเอาชนะ BasicObject#method_missing
วิธีการ
คาดเดาอะไร? เป็นการดีที่จะแทนที่ method_missing
เนื่องจากวิธีนี้ขึ้นอยู่กับกลไกของ Method Lookup Path . ข้อแตกต่างเพียงอย่างเดียวกับวิธีปกติคือ เราแน่ใจว่าจะพบวิธีนี้อย่างน้อยหนึ่งครั้งโดยเส้นทางการค้นหาวิธีการ .
อันที่จริง BasicObject class
— ซึ่งเป็นคลาสรูทของคลาสใดๆ ใน Ruby — กำหนดเวอร์ชันขั้นต่ำของวิธีนี้ Classic Ruby Magic ไม่เป็นไร
มาแทนที่วิธีนี้ใน Collection class
:
class Collection
def initialize
@collection = {}
end
def method_missing(method_id, *args)
if method_id[-1] == '='
key = method_id[0..-2]
@collection[key.to_sym] = args.first
else
@collection[method_id]
end
end
end
collection = Collection.new
collection.obj1 = 'value1'
collection.obj2 = 'value2'
collection.obj1 # => 'value1'
collection.obj2 # => 'value2'
ที่นี่ Collection#method_missing
ทำหน้าที่เป็นตัวแทนให้กับ @collection
ตัวแปรอินสแตนซ์ อันที่จริง นี่เป็นวิธีที่ Ruby จัดการกับการมอบหมายอ็อบเจ็กต์อย่างคร่าวๆ — c.f:delegate
ห้องสมุด
หากวิธีการที่หายไปคือวิธี setter (collection.obj1 = 'value1'
) จากนั้นชื่อเมธอด (:obj1
) ถูกใช้เป็นคีย์และอาร์กิวเมนต์ ('value1'
) เป็นค่าของ @collection
รายการแฮช (@collection[:obj1] = 'value1'
)
เครื่องมือสร้างแท็ก HTML
ตอนนี้เรารู้แล้วว่า method_missing
. เป็นอย่างไร วิธีการทำงานเบื้องหลัง มาปรับใช้กรณีการใช้งานที่ทำซ้ำได้
ที่นี่ เป้าหมายคือการกำหนด DSL ต่อไปนี้:
HTML.p 'hello world' # => <p>hello world</p>
HTML.div 'hello world' # => <div>hello world</div>
HTML.h1 'hello world' # => <h1>hello world</h1>
HTML.h2 'hello world' # => <h2>hello world</h2>
HTML.span 'hello world' # => <span>hello world</span>
HTML.p "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>
ในการทำเช่นนั้น เราจะใช้ HTML.method_missing
เมธอด เพื่อหลีกเลี่ยงการกำหนดเมธอดสำหรับแท็ก HTML แต่ละแท็ก
ขั้นแรก เรากำหนด HTML
โมดูล. จากนั้นเราจะกำหนด method_missing
วิธีการเรียนในโมดูลนี้:
module HTML
def HTML.method_missing(method_id, *args, &block)
"<#{method_id}>#{args.first}</#{method_id}>"
end
end
วิธีการของเราจะสร้างแท็ก HTML โดยใช้ method_id
. ที่หายไป — :div
สำหรับการเรียก HTML.div
ยกตัวอย่าง
โปรดทราบว่าเมธอดของคลาสนั้นขึ้นอยู่กับเมธอด Lookup Path ด้วย
เราสามารถปรับปรุงโปรแกรมสร้างแท็ก HTML ของเราได้โดย:
- ใช้อาร์กิวเมนต์บล็อกเพื่อจัดการแท็กที่ซ้อนกัน
- การจัดการแท็กเดี่ยว —
<br/>
ตัวอย่างเช่น
แต่โปรดทราบว่าด้วยโค้ดไม่กี่บรรทัด เราสามารถสร้างแท็ก HTML ได้จำนวนมาก
สรุป:
method_missing
เป็นจุดเริ่มต้นที่ดีในการสร้าง DSL โดยที่คำสั่งส่วนใหญ่จะแชร์ชุดของรูปแบบที่ระบุ
บทสรุป
เราเดินขึ้นไปตามสายบรรพบุรุษใน Ruby และเข้าสู่ BasicObject#method_missing
. BasicObject#method_missing
เป็นส่วนหนึ่งของ วิธี Ruby Hook . ใช้เพื่อโต้ตอบกับวัตถุในช่วงเวลาที่แน่นอนในวงจรชีวิต เช่นเดียวกับ วิธี Ruby Hook อื่นๆ ต้องใช้วิธีการเบ็ดนี้อย่างระมัดระวัง และด้วยความระมัดระวัง เราหมายความว่าไม่ควรปรับเปลี่ยนพฤติกรรมของ Ruby Object Model —ยกเว้นว่าคุณกำลังเล่นหรือเขียนบล็อกโพสต์อยู่;-)
ว้าว!