Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เพิ่มห่วงโซ่บรรพบุรุษด้วย method_missing

ถือกระเป๋าถือขึ้นเครื่องเพราะวันนี้เราจะเดินทางไปจนถึงห่วงโซ่บรรพบุรุษ เราจะติดตามการเรียกเมธอดและดูว่ามันจะขึ้นกับเชนได้อย่างไร และดูว่าเกิดอะไรขึ้นหากเมธอดนั้นหายไป และเพราะว่าเราชอบเล่นกับไฟ เราจะไม่หยุดเพียงแค่นั้น แต่ไปต่อที่ เล่นกับไฟ แทนที่ 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 —ยกเว้นว่าคุณกำลังเล่นหรือเขียนบล็อกโพสต์อยู่;-)

ว้าว!