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

เปลี่ยนวิธีสร้างวัตถุให้ทับทิม

สิ่งหนึ่งที่ทำให้ 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