แก้ไข : เป็นเวลา 9 ปีแล้วตั้งแต่แรกที่ฉันเขียนคำตอบนี้และสมควรได้รับการผ่าตัดเพื่อความงามเพื่อให้เป็นปัจจุบัน
คุณสามารถดูรุ่นสุดท้ายก่อนที่จะแก้ไขที่นี่
คุณไม่สามารถเรียกวิธีการเขียนทับโดยใช้ชื่อหรือคำสำคัญ นั่นเป็นหนึ่งในหลาย ๆ เหตุผลที่ควรหลีกเลี่ยงการปะลิงและควรได้รับการสืบทอดแทนเนื่องจากเห็นได้ชัดว่าคุณสามารถเรียกวิธีการแทนที่ได้
หลีกเลี่ยงการปะลิง
มรดก
ดังนั้นถ้าเป็นไปได้คุณควรจะชอบสิ่งนี้:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
วิธีนี้ใช้ได้ผลถ้าคุณควบคุมการสร้างFoo
วัตถุ เพียงแค่เปลี่ยนสถานที่ซึ่งจะสร้างทุกแทนที่จะสร้างFoo
ExtendedFoo
วิธีนี้จะทำงานได้ดียิ่งขึ้นถ้าคุณใช้รูปแบบการออกแบบการพึ่งพา , รูปแบบการออกแบบวิธีการของโรงงาน , รูปแบบการออกแบบโรงงานนามธรรมหรือบางสิ่งบางอย่างตามเส้นเหล่านั้นเพราะในกรณีนี้มีเพียงสถานที่ที่คุณต้องการเปลี่ยน
คณะผู้แทน
หากคุณไม่ได้ควบคุมการสร้างFoo
วัตถุเช่นเพราะพวกเขาถูกสร้างขึ้นโดยกรอบที่อยู่นอกการควบคุมของคุณ (เช่นทับทิมบนรางตัวอย่าง) จากนั้นคุณสามารถใช้รูปแบบการออกแบบแรปเปอร์ :
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
โดยทั่วไปที่ขอบเขตของระบบที่ได้Foo
วัตถุเข้ามาในรหัสของคุณคุณห่อมันเป็นวัตถุอื่นแล้วใช้ว่าวัตถุแทนของเดิมทุกที่อื่นในรหัสของคุณ
นี่ใช้Object#DelegateClass
เมธอดตัวช่วยจากdelegate
ไลบรารีใน stdlib
“ สะอาด” การปะลิง
สองวิธีข้างต้นจำเป็นต้องเปลี่ยนระบบเพื่อหลีกเลี่ยงการปะแก้ลิง ส่วนนี้แสดงวิธีการที่ต้องการและอย่างน้อยที่สุดของการปะแก้ลิงหากการเปลี่ยนระบบไม่ใช่ตัวเลือก
Module#prepend
ถูกเพิ่มเพื่อรองรับกรณีการใช้งานนี้มากหรือน้อย Module#prepend
ทำสิ่งเดียวกันModule#include
ยกเว้นยกเว้นผสมใน mixin ใต้ชั้นเรียนโดยตรง:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
หมายเหตุ: ฉันยังเขียนเล็กน้อยเกี่ยวกับModule#prepend
ในคำถามนี้: โมดูลทับทิม prepend vs derivation
มรดก Mixin (แตก)
ฉันเคยเห็นบางคนลอง (และถามว่าทำไมมันไม่ทำงานที่นี่ใน StackOverflow) บางอย่างเช่นinclude
ไอเอ็นจีมิกซ์อินแทนที่จะเป็นprepend
ไอเอ็นจี:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
น่าเสียดายที่มันใช้ไม่ได้ super
มันเป็นความคิดที่ดีเพราะใช้มรดกซึ่งหมายความว่าคุณสามารถใช้ อย่างไรก็ตามModule#include
แทรก mixin เหนือคลาสในลำดับชั้นการสืบทอดซึ่งหมายความว่าFooExtensions#bar
จะไม่ถูกเรียก (และถ้ามันถูกเรียกมันsuper
จะไม่อ้างถึงจริง ๆFoo#bar
แต่แทนที่จะObject#bar
ไม่มีอยู่) เนื่องจากFoo#bar
จะพบก่อนเสมอ
วิธีการห่อ
คำถามใหญ่คือ: เราจะยึดมั่นในbar
วิธีการโดยไม่ต้องรักษาวิธีการจริง ? คำตอบอยู่ที่บ่อยครั้งในการเขียนโปรแกรมเชิงฟังก์ชัน เราได้รับวิธีการเป็นวัตถุจริงและเราใช้การปิด (เช่นบล็อก) เพื่อให้แน่ใจว่าเราและเราเท่านั้นที่ยึดมั่นกับวัตถุนั้น:
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
มันสะอาดมาก: เนื่องจากold_bar
เป็นเพียงตัวแปรท้องถิ่นมันจะออกไปนอกขอบเขตที่ส่วนท้ายของคลาสและไม่สามารถเข้าถึงได้จากทุกที่แม้แต่ใช้การสะท้อน! และเนื่องจากModule#define_method
ใช้บล็อกและบล็อกที่อยู่ใกล้กับสภาพแวดล้อมคำศัพท์รอบตัว (ซึ่งเป็นสาเหตุที่เราใช้define_method
แทนที่def
นี่) มัน (และมีเพียง ) ที่จะยังคงสามารถเข้าถึงold_bar
ได้แม้ว่าจะออกไปนอกขอบเขตแล้ว
คำอธิบายสั้น ๆ :
old_bar = instance_method(:bar)
ที่นี่เราจะห่อbar
วิธีการเป็นวัตถุวิธีการและกำหนดให้กับตัวแปรท้องถิ่นUnboundMethod
old_bar
ซึ่งหมายความว่าตอนนี้เรามีวิธีการที่จะยึดมั่นต่อไปbar
แม้หลังจากที่มันถูกเขียนทับ
old_bar.bind(self)
นี่เป็นเรื่องยุ่งยากเล็กน้อย โดยทั่วไปใน Ruby (และในภาษา OO ที่ใช้การจัดส่งแบบเดี่ยว) วิธีการหนึ่งจะผูกกับวัตถุตัวรับเฉพาะที่เรียกว่าself
ใน Ruby กล่าวอีกนัยหนึ่ง: วิธีการมักจะรู้ว่าสิ่งที่มันถูกเรียกมันก็รู้ว่ามันself
คืออะไร แต่เราคว้าวิธีโดยตรงจากชั้นเรียนรู้ได้อย่างไรว่ามันself
คืออะไร?
ดีมันไม่ได้ซึ่งเป็นเหตุผลที่เราต้องbind
ของเราUnboundMethod
ไปยังวัตถุที่เป็นครั้งแรกซึ่งจะกลับมาเป็นMethod
วัตถุที่เรานั้นสามารถเรียก ( UnboundMethod
ไม่สามารถเรียกได้เพราะพวกเขาไม่รู้ว่าจะทำอย่างไรโดยไม่รู้self
ตัว)
แล้วเราจะทำอย่างไรbind
ต่อ เราเพียง แต่bind
มันกับตัวเองด้วยวิธีการที่จะทำงานตรงเหมือนเดิมbar
จะมี!
สุดท้ายเราต้องเรียกว่าถูกส่งกลับจากMethod
bind
ใน Ruby 1.9 มีไวยากรณ์ใหม่ที่ดีสำหรับ ( .()
) แต่ถ้าคุณใช้ 1.8 คุณสามารถใช้call
วิธีได้ นั่นคือสิ่งที่.()
ได้รับการแปลแล้ว
ต่อไปนี้เป็นคำถามอื่นสองสามข้อที่แนวคิดเหล่านี้อธิบายไว้บางส่วน:
การปะของลิง“ สกปรก”
ปัญหาที่เราประสบกับการปะแก้ลิงของเราคือเมื่อเราเขียนทับวิธีวิธีนั้นหายไปดังนั้นเราจึงไม่สามารถเรียกมันได้อีกต่อไป ดังนั้นขอทำสำเนาสำรอง!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
ปัญหาของสิ่งนี้คือตอนนี้เราได้ทำให้เนมสเปซสกปรกด้วยold_bar
วิธีฟุ่มเฟือย วิธีนี้จะแสดงในเอกสารของเรามันจะแสดงในการเติมโค้ดใน IDE ของเรามันจะปรากฏขึ้นในระหว่างการไตร่ตรอง นอกจากนี้มันยังสามารถถูกเรียกได้ แต่สันนิษฐานว่าเราลิงปะติดมันเพราะเราไม่ชอบพฤติกรรมของมันในตอนแรกดังนั้นเราอาจไม่ต้องการให้คนอื่นเรียกมัน
แม้จะมีความจริงที่ว่านี้มีคุณสมบัติที่ไม่พึงประสงค์บางอย่างมันได้กลายเป็นโชคร้ายที่นิยมผ่าน Module#alias_method_chain
AciveSupport
ในกรณีที่คุณต้องการพฤติกรรมที่แตกต่างกันในบางสถานที่เท่านั้นและไม่สามารถใช้ได้ทั่วทั้งระบบคุณสามารถใช้การปรับแต่งเพื่อ จำกัด การปะของลิงให้อยู่ในขอบเขตที่เฉพาะเจาะจง ฉันจะสาธิตที่นี่โดยใช้Module#prepend
ตัวอย่างจากด้านบน:
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
คุณสามารถดูตัวอย่างที่ซับซ้อนยิ่งขึ้นของการใช้การปรับแต่งในคำถามนี้: วิธีเปิดใช้งานการปะของลิงสำหรับวิธีการเฉพาะได้อย่างไร
ความคิดที่ถูกทิ้งร้าง
ก่อนที่ชุมชน Ruby จะตัดสินModule#prepend
มีหลายแนวคิดที่ลอยอยู่รอบ ๆ ซึ่งคุณอาจเห็นการอ้างอิงในการสนทนาที่เก่ากว่า Module#prepend
ทั้งหมดเหล่านี้จะวิทย
ผู้ประสานวิธีการ
แนวคิดหนึ่งคือแนวคิดของ combinators วิธีการจาก CLOS นี่เป็นรุ่นย่อยที่มีน้ำหนักเบามากของการเขียนโปรแกรม Aspect-Oriented
ใช้ไวยากรณ์เช่น
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
คุณจะสามารถ“ เข้าหา” การดำเนินการของbar
วิธีการได้
อย่างไรก็ตามยังไม่ชัดเจนว่าคุณจะเข้าถึงbar
ค่าตอบแทนภายในbar:after
ได้อย่างไรและอย่างไร บางทีเรา (ab) ใช้super
คำหลัก?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
การแทนที่
คอมprepend
บิเนเตอร์ก่อนหน้านั้นเทียบเท่ากับมิกซ์อินด้วยวิธีการเอาชนะที่เรียกได้super
ที่ส่วนท้ายสุดของวิธี ในทำนองเดียวกันหลังจาก Combinator เทียบเท่ากับprepend
ไอเอ็นจี mixin กับวิธีการเอาชนะว่าสายsuper
ที่มากจุดเริ่มต้นของวิธีการ
นอกจากนี้คุณยังสามารถทำสิ่งต่าง ๆ ก่อนและหลังการโทรsuper
คุณสามารถโทรได้super
หลายครั้งและทั้งดึงและจัดการsuper
ค่าส่งคืนของทำให้prepend
มีประสิทธิภาพมากกว่า combinators วิธี
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
และ
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old
คำสำคัญ
แนวคิดนี้เพิ่มคำหลักใหม่ที่คล้ายกับsuper
ซึ่งช่วยให้คุณสามารถเรียกใช้วิธีการเขียนทับแบบเดียวกับที่super
ให้คุณเรียกวิธีการเขียนทับ :
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
ปัญหาหลักของเรื่องนี้คือมันย้อนกลับไม่เข้ากัน: ถ้าคุณมีวิธีการที่เรียกว่าold
คุณจะไม่สามารถเรียกมันได้อีกต่อไป!
การแทนที่
super
ในวิธีการเอาชนะในprepend
mixin ed เป็นหลักเช่นเดียวกับold
ในข้อเสนอนี้
redef
คำสำคัญ
คล้ายกับข้างต้น แต่แทนที่จะเพิ่มคำหลักใหม่สำหรับการเรียกใช้วิธีการเขียนทับและปล่อยให้def
อยู่คนเดียวเราเพิ่มคำหลักใหม่สำหรับวิธีการกำหนดใหม่ สิ่งนี้เข้ากันได้ย้อนหลังเนื่องจากไวยากรณ์ปัจจุบันผิดกฎหมายอยู่แล้ว:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
แทนที่จะเพิ่มคำหลักใหม่สองคำเราสามารถนิยามความหมายsuper
ภายในใหม่ได้redef
:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
การแทนที่
redef
การ ining method นั้นเทียบเท่ากับการแทนที่ method ในprepend
mixin ed super
ในวิธีการเอาชนะพฤติกรรมเช่นsuper
หรือold
ในข้อเสนอนี้