สิ่งหนึ่งที่ทำให้ Ruby ยอดเยี่ยมคือเราสามารถปรับแต่งเกือบทุกอย่างตามความต้องการของเรา สิ่งนี้มีประโยชน์และเป็นอันตราย การยิงตัวเองเป็นเรื่องง่าย แต่เมื่อใช้อย่างระมัดระวัง อาจส่งผลให้มีวิธีแก้ปัญหาที่ทรงประสิทธิภาพ
ที่ Ruby Magic เราคิดว่ามีประโยชน์และอันตรายเป็นการผสมผสานที่ยอดเยี่ยม มาดูกันว่า Ruby สร้างและเริ่มต้นวัตถุอย่างไร และเราจะแก้ไขพฤติกรรมเริ่มต้นได้อย่างไร
พื้นฐานของการสร้างออบเจกต์ใหม่จากคลาส
ในการเริ่มต้น มาดูวิธีการสร้างวัตถุใน Ruby กัน เพื่อสร้าง วัตถุ . ใหม่ (หรือ ตัวอย่าง ) เราเรียก new
ในชั้นเรียน ต่างจากภาษาอื่น new
ไม่ใช่คีย์เวิร์ดของภาษา แต่เป็นเมธอดที่เรียกได้เหมือนกัน
class Dog
end
object = Dog.new
เพื่อปรับแต่งวัตถุที่สร้างขึ้นใหม่ สามารถส่งอาร์กิวเมนต์ไปยัง new
กระบวนการ. สิ่งที่ถูกส่งผ่านเป็นอาร์กิวเมนต์ จะถูกส่งต่อไปยังตัวเริ่มต้น
class Dog
def initialize(name)
@name = name
end
end
object = Dog.new('Good boy')
ไม่เหมือนภาษาอื่น ๆ ตัวเริ่มต้นใน Ruby เป็นเพียงวิธีการแทนไวยากรณ์หรือคำหลักพิเศษบางอย่าง
เมื่อคำนึงถึงสิ่งนั้นแล้ว ไม่ควรยุ่งกับวิธีการเหล่านั้น เหมือนกับที่ทำกับวิธีอื่นของ Ruby หรือไม่? แน่นอน!
การปรับเปลี่ยนพฤติกรรมของวัตถุชิ้นเดียว
สมมติว่าเราต้องการให้แน่ใจว่าอ็อบเจ็กต์ทั้งหมดของคลาสใดคลาสหนึ่งจะพิมพ์คำสั่งบันทึกเสมอ แม้ว่าเมธอดจะถูกแทนที่ในคลาสย่อยก็ตาม วิธีหนึ่งในการทำเช่นนี้คือการเพิ่มโมดูลในคลาสซิงเกิลตันของออบเจ็กต์
module Logging
def make_noise
puts "Started making noise"
super
puts "Finished making noise"
end
end
class Bird
def make_noise
puts "Chirp, chirp!"
end
end
object = Bird.new
object.singleton_class.include(Logging)
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
ในตัวอย่างนี้ Bird
วัตถุถูกสร้างขึ้นโดยใช้ Bird.new
และ Logging
โมดูลรวมอยู่ในวัตถุผลลัพธ์โดยใช้คลาสซิงเกิลตัน
คลาสซิงเกิลตันคืออะไร
Ruby อนุญาตให้ใช้เมธอดเฉพาะสำหรับอ็อบเจ็กต์เดียว เพื่อสนับสนุนสิ่งนี้ Ruby ได้เพิ่มคลาสที่ไม่ระบุชื่อระหว่างอ็อบเจ็กต์และคลาสจริง เมื่อเรียกเมธอด เมธอดที่กำหนดไว้ในคลาสซิงเกิลตันจะมีความสำคัญเหนือเมธอดในคลาสจริง คลาสซิงเกิลตันเหล่านี้มีเอกลักษณ์เฉพาะสำหรับทุกอ็อบเจ็กต์ ดังนั้นการเพิ่มเมธอดในคลาสเหล่านี้จึงไม่มีผลกับอ็อบเจ็กต์อื่นๆ ของคลาสจริง เรียนรู้เพิ่มเติมเกี่ยวกับคลาสและอ็อบเจ็กต์ในคู่มือ Programming Ruby
การปรับเปลี่ยนคลาสซิงเกิลตันของแต่ละอ็อบเจ็กต์นั้นค่อนข้างยุ่งยากทุกครั้งที่ถูกสร้างขึ้น เรามาย้ายการรวมของ Logging
. กันเถอะ คลาสไปยัง initializer เพื่อเพิ่มให้กับทุกอ็อบเจ็กต์ที่สร้างขึ้น
module Logging
def make_noise
puts "Started making noise"
super
puts "Finished making noise"
end
end
class Bird
def initialize
singleton_class.include(Logging)
end
def make_noise
puts "Chirp, chirp!"
end
end
object = Bird.new
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
ขณะนี้ใช้งานได้ดี หากเราสร้างคลาสย่อยของ Bird
เช่น Duck
ตัวเริ่มต้นของมันจำเป็นต้องเรียก super
เพื่อเก็บ Logging
พฤติกรรม. แม้ว่าใครจะโต้แย้งได้ว่าควรเรียก super
. อย่างถูกต้องเสมอ เมื่อใดก็ตามที่มีการแก้ไขเมธอด ให้ลองหาวิธีที่ไม่ต้องการ มัน.
ถ้าเราไม่เรียก super
จาก subclass เราสูญเสียการรวม Logger
คลาส:
class Duck < Bird
def initialize(name)
@name = name
end
def make_noise
puts "#{@name}: Quack, quack!"
end
end
object = Duck.new('Felix')
object.make_noise
# Felix: Quack, quack!
เรามาแทนที่ Bird.new
. แทน . ดังที่ได้กล่าวมาแล้ว new
เป็นเพียงวิธีการที่ใช้ในชั้นเรียน เพื่อให้เราสามารถแทนที่มัน เรียก super และแก้ไขวัตถุที่สร้างขึ้นใหม่ตามความต้องการของเรา
class Bird
def self.new(*arguments, &block)
instance = super
instance.singleton_class.include(Logging)
instance
end
end
object = Duck.new('Felix')
object.make_noise
# Started making noise
# Felix: Quack, quack!
# Finished making noise
แต่จะเกิดอะไรขึ้นเมื่อเราเรียก make_noise
ในตัวเริ่มต้น? น่าเสียดาย เนื่องจากคลาส singleton ไม่มี Logging
โมดูลยังไม่ได้รับผลลัพธ์ที่ต้องการ
โชคดีที่มีวิธีแก้ไข:เป็นไปได้ที่จะสร้าง .new
เริ่มต้น พฤติกรรมตั้งแต่เริ่มต้นโดยการเรียก allocate
.
class Bird
def self.new(*arguments, &block)
instance = allocate
instance.singleton_class.include(Logging)
instance.send(:initialize, *arguments, &block)
instance
end
end
กำลังโทร allocate
ส่งคืนวัตถุใหม่ที่ยังไม่ได้กำหนดค่าของคลาส หลังจากนั้น เราสามารถรวมพฤติกรรมเพิ่มเติม จากนั้นจึงเรียก initialize
วิธีการบนวัตถุนั้น (เพราะ initialize
เป็นส่วนตัวโดยปริยาย เราต้องใช้ send
สำหรับสิ่งนี้)
ความจริงเกี่ยวกับ Class#allocate
ต่างจากวิธีอื่นๆ ตรงที่ไม่สามารถแทนที่ allocate
. Ruby ไม่ได้ใช้วิธีการทั่วไปในการ allocate
ภายใน เป็นผลให้เพียงแค่แทนที่ allocate
โดยไม่ต้องแทนที่ new
ไม่ทำงาน อย่างไรก็ตาม หากเราเรียก allocate
โดยตรง Ruby จะเรียกวิธีการกำหนดใหม่ เรียนรู้เพิ่มเติมเกี่ยวกับ Class#new
และ Class#allocate
ในเอกสารของ Ruby
ทำไมเราต้องทำเช่นนี้?
เช่นเดียวกับหลายๆ อย่าง การปรับเปลี่ยนวิธีที่ Ruby สร้างออบเจ็กต์จากคลาสอาจเป็นอันตรายและสิ่งต่าง ๆ อาจเสียหายในลักษณะที่ไม่คาดคิด
อย่างไรก็ตาม มีกรณีการใช้งานที่ถูกต้องสำหรับการเปลี่ยนแปลงการสร้างอ็อบเจ็กต์ ตัวอย่างเช่น ActiveRecord ใช้ allocate
ด้วย init_from_db
. อื่น วิธีการเปลี่ยนกระบวนการเริ่มต้นเมื่อสร้างวัตถุจากฐานข้อมูลซึ่งตรงข้ามกับการสร้างวัตถุที่ยังไม่ได้บันทึก นอกจากนี้ยังใช้ allocate
เพื่อแปลงระเบียนระหว่างประเภทการสืบทอดตารางเดียวที่แตกต่างกันด้วย becomes
.
ที่สำคัญที่สุด โดยการเล่นรอบกับการสร้างวัตถุ คุณจะได้รับข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานของ Ruby และเปิดใจรับวิธีแก้ปัญหาต่างๆ เราหวังว่าคุณจะสนุกกับบทความนี้
เราชอบที่จะได้ยินเกี่ยวกับสิ่งที่คุณนำไปใช้โดยเปลี่ยนวิธีการสร้างวัตถุเริ่มต้นของ Ruby โปรดอย่าลังเลที่จะทวีตความคิดเห็นของคุณไปที่ @AppSignal