ความแตกต่างระหว่างการรวมและการขยายในทับทิมคืออะไร?


415

เพิ่งได้รับหัวของฉันเกี่ยวกับการเขียนโปรแกรมทับทิม mixin / modules จัดการเพื่อสร้างความสับสนให้ฉันเสมอ

  • รวม : ผสมในวิธีการโมดูลที่ระบุเป็นวิธีการอินสแตนซ์ในชั้นเรียนเป้าหมาย
  • Extended : ผสมในวิธีการโมดูลที่ระบุว่าเป็นวิธีการเรียนในชั้นเรียนเป้าหมาย

ดังนั้นความแตกต่างที่สำคัญเพียงแค่นี้หรือเป็นมังกรที่ซุ่มซ่อนขนาดใหญ่กว่า? เช่น

module ReusableModule
  def module_method
    puts "Module Method: Hi there!"
  end
end

class ClassThatIncludes
  include ReusableModule
end
class ClassThatExtends
  extend ReusableModule
end

puts "Include"
ClassThatIncludes.new.module_method       # "Module Method: Hi there!"
puts "Extend"
ClassThatExtends.module_method            # "Module Method: Hi there!"

ตรวจสอบลิงค์นี้ด้วย: juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Donato

คำตอบ:


249

สิ่งที่คุณพูดถูกต้อง อย่างไรก็ตามมีมากกว่านั้น

หากคุณมีระดับKlazzและโมดูลModรวมทั้งModในการKlazzให้กรณีของKlazzการเข้าถึงModวิธีการ 's หรือคุณสามารถขยายKlazzด้วยการModให้ชั้นเรียน Klazzเข้าถึงModวิธีการของ o.extend Modแต่ยังให้คุณสามารถขยายวัตถุโดยพลการ ในกรณีนี้วัตถุส่วนบุคคลจะได้รับModวิธีการแม้ว่าวัตถุอื่น ๆ ทั้งหมดที่มีระดับเดียวกันเป็นoไม่


324

ต่ออายุ - เพิ่มวิธีการของโมดูลที่ระบุและค่าคงที่ให้กับ metaclass ของเป้าหมาย (เช่นคลาสซิงเกิลตัน) เช่น

  • ถ้าคุณโทร Klazz.extend(Mod)ตอนนี้ Klazz มีวิธีการ Mod (เป็นวิธีการเรียน)
  • ถ้าคุณโทรobj.extend(Mod)ตอนนี้ obj มีวิธีการของ Mod (เป็นวิธีการแบบอินสแตนซ์) แต่ไม่มีอินสแตนซ์อื่นของobj.classวิธีการเหล่านั้นถูกเพิ่ม
  • extend เป็นวิธีการของประชาชน

รวม - โดยค่าเริ่มต้นมันผสมในวิธีการของโมดูลที่ระบุเป็นวิธีการอินสแตนซ์ในโมดูล / ชั้นเรียนเป้าหมาย เช่น

  • ถ้าคุณโทร class Klazz; include Mod; end;ตอนนี้อินสแตนซ์ทั้งหมดของ Klazz จะสามารถเข้าถึงวิธีการของ Mod (เป็นวิธีการแบบอินสแตนซ์)
  • include เป็นวิธีการส่วนตัวเนื่องจากมีวัตถุประสงค์เพื่อเรียกใช้จากภายในคลาสคอนเทนเนอร์ / โมดูล

อย่างไรก็ตามโมดูลมักจะแทนที่ includeพฤติกรรมของลิงมากโดยincludedวิธีการแก้ไขลิง นี่คือสิ่งที่โดดเด่นมากในรหัส Rails ดั้งเดิม รายละเอียดเพิ่มเติมจาก Yehuda Katzรายละเอียดเพิ่มเติมจากฮุดะแคทซ์

รายละเอียดเพิ่มเติมเกี่ยวincludeกับพฤติกรรมเริ่มต้นของสมมติว่าคุณได้ใช้รหัสต่อไปนี้

class Klazz
  include Mod
end
  • หาก Mod รวมอยู่ใน Klazz หรือหนึ่งในบรรพบุรุษของมันแล้วคำสั่ง include จะไม่มีผลใด ๆ
  • นอกจากนี้ยังรวมถึงค่าคงที่ของ Mod ใน Klazz ตราบใดที่ยังไม่ปะทะกัน
  • มันให้การเข้าถึง Klazz กับตัวแปรโมดูลของ Mod เช่น@@fooหรือ@@bar
  • เพิ่ม ArgumentError หากมีวงจรรวมอยู่ด้วย
  • แนบโมดูลเป็นบรรพบุรุษทันทีของผู้โทร (เช่นมันเพิ่ม Mod ให้กับ Klazz.ancestors แต่ Mod ไม่ได้ถูกเพิ่มเข้าไปในห่วงโซ่ของ Klazz.superclass.superclass.superclass.superclass ดังนั้นการโทรsuperใน Klazz # foo จะตรวจสอบ Mod # foo ก่อนตรวจสอบ สู่วิธีการ foo ของ superclass ที่แท้จริงของ Klazz ดู RubySpec เพื่อดูรายละเอียด)

แน่นอนว่าเอกสารทับทิมเป็นสิ่งที่ดีที่สุดสำหรับสิ่งเหล่านี้ โครงการ RubySpecเป็นทรัพยากรที่ยอดเยี่ยมเพราะพวกเขาบันทึกการทำงานอย่างแม่นยำ


22
ฉันรู้ว่านี่เป็นโพสต์ที่ค่อนข้างเก่า แต่ความชัดเจนของการตอบกลับไม่สามารถยับยั้งไม่ให้ฉันแสดงความคิดเห็น ขอบคุณมากสำหรับคำอธิบายที่ดี
MohamedSanaulla

2
@anwar ชัดเจน แต่ตอนนี้ฉันสามารถแสดงความคิดเห็นและฉันจัดการเพื่อค้นหาบทความอีกครั้ง มีให้ที่นี่: aaronlasseigne.com/2012/01/01/17/explaining-include-and-extend และฉันยังคิดว่าสคีมาทำให้เข้าใจง่ายขึ้นมาก
systho

1
ชัยชนะครั้งใหญ่ในการตอบสนองนี้คือวิธีการextendใช้วิธีการเป็นวิธีการเรียนหรืออินสแตนซ์ขึ้นอยู่กับการใช้งาน Klass.extend= เมธอดคลาส, objekt.extend= เมธอดอินสแตนซ์ ฉันมักจะ (ผิด) วิธีการเรียนสันนิษฐานว่ามาจากและตัวอย่างจากextend include
Frank Koehl

16

ถูกต้อง.

เบื้องหลังการรวมเป็นจริงนามแฝงสำหรับappend_featuresซึ่ง (จากเอกสาร):

การใช้งานเริ่มต้นของรูบี้คือการเพิ่มค่าคงที่วิธีการและตัวแปรโมดูลของโมดูลนี้ไปยัง aModule หากโมดูลนี้ยังไม่ได้ถูกเพิ่มไปยัง aModule หรือหนึ่งในบรรพบุรุษ


4

เมื่อคุณincludeโมดูลเข้าไปในชั้นเรียนวิธีโมดูลจะถูกนำเข้าเป็นวิธีการเช่น

อย่างไรก็ตามเมื่อคุณextendโมดูลเข้าไปในชั้นเรียนวิธีโมดูลจะถูกนำเข้าเป็นวิธีการเรียน

ตัวอย่างเช่นถ้าเรามีโมดูลที่Module_testกำหนดดังนี้:

module Module_test
  def func
    puts "M - in module"
  end
end

ตอนนี้สำหรับincludeโมดูล ถ้าเรากำหนดคลาสAดังนี้:

class A
  include Module_test
end

a = A.new
a.func

M - in moduleการส่งออกจะได้รับ:

ถ้าเราแทนที่บรรทัดinclude Module_testด้วยและเรียกใช้รหัสอีกครั้งที่เราได้รับข้อผิดพลาดต่อไปนี้:extend Module_testundefined method 'func' for #<A:instance_num> (NoMethodError)

การเปลี่ยนวิธีการเรียกa.funcเพื่อการส่งออกที่มีการเปลี่ยนแปลงไปที่:A.funcM - in module

จากโค้ดข้างต้นก็เป็นที่ชัดเจนว่าเมื่อเราincludeโมดูลวิธีการของมันกลายเป็นวิธีการเช่นและเมื่อเราextendโมดูลวิธีการของมันกลายเป็นวิธีการเรียน


3

คำตอบอื่น ๆ ทั้งหมดเป็นสิ่งที่ดีรวมถึงเคล็ดลับในการขุดผ่าน RubySpecs:

https://github.com/rubyspec/rubyspec/blob/master/core/module/include_spec.rb

https://github.com/rubyspec/rubyspec/blob/master/core/module/extend_object_spec.rb

สำหรับกรณีการใช้งาน:

หากคุณรวมโมดูล ReusableModule ในคลาส ClassThatIncludes วิธีการค่าคงที่คลาสคลาสย่อยและการประกาศอื่น ๆ จะได้รับการอ้างอิง

ถ้าคุณขยาย ClassThatExtends ระดับกับโมดูล ReusableModule แล้ววิธีการและค่าคงที่ได้รับการคัดลอก เห็นได้ชัดว่าถ้าคุณไม่ระวังคุณอาจเสียความทรงจำมากมายด้วยการทำซ้ำคำจำกัดความแบบไดนามิก

ถ้าคุณใช้ ActiveSupport :: Concern, ฟังก์ชั่น. included () จะช่วยให้คุณเขียนคลาสที่รวมถึงใหม่ได้โดยตรง โมดูล ClassMethods ภายในข้อกังวลได้รับการขยาย (คัดลอก) ลงในคลาสที่รวม


1

ฉันต้องการอธิบายกลไกขณะใช้งาน หากฉันไม่ถูกต้องโปรดแก้ไขให้ถูกต้อง

เมื่อเราใช้includeเราจะเพิ่มการเชื่อมโยงจากชั้นเรียนของเราไปยังโมดูลซึ่งมีวิธีการบางอย่าง

class A
include MyMOd
end

a = A.new
a.some_method

วัตถุไม่มีวิธีการมีเพียงส่วนคำสั่งและโมดูลเท่านั้น ดังนั้นเมื่อaได้รับ mesage some_methodจะเริ่มวิธีการค้นหาsome_methodในaคลาส eigen จากนั้นในAคลาสและจากนั้นเชื่อมโยงกับAโมดูลคลาสหากมีบางอย่าง (ในลำดับย้อนกลับรวมชนะครั้งสุดท้าย)

เมื่อเราใช้extendเราจะเพิ่มการเชื่อมโยงไปยังโมดูลในคลาส eigen ของวัตถุ ดังนั้นถ้าเราใช้ A.new.extend (MyMod) เรากำลังเพิ่มการเชื่อมโยงไปยังโมดูลของเราในคลาสหรือa'คลาสอินสแตนซ์ของ Aigen และถ้าเราใช้ A.extend (MyMod) เราจะเพิ่มการเชื่อมโยงไปยัง A (ของวัตถุชั้นเรียนนอกจากนี้ยังมีวัตถุ) A'eigenclass

ดังนั้นพา ธ การค้นหาเมธอดaจะเป็นดังนี้: a => a '=> โมดูลที่ลิงก์กับ' class => A.

นอกจากนี้ยังมีวิธีการเสริมซึ่งเปลี่ยนเส้นทางการค้นหา:

a => a '=> โมดูล่าแบบรวม A => A => โมดูลที่รวมไว้ใน A

ขอโทษสำหรับภาษาอังกฤษที่ไม่ดีของฉัน

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