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

การกำหนดขอบเขตคำศัพท์และตัวแปรคลาส Ruby

ตัวแปรคลาสของ Ruby ทำให้เกิดความสับสน แม้แต่ผู้ใช้ Ruby ที่เชี่ยวชาญก็พบว่าพวกเขาเข้าใจยาก ตัวอย่างที่ชัดเจนที่สุดเกี่ยวกับความเฉื่อย:

class Fruit
  @@kind = nil

  def self.kind
    @@kind
  end
end

class Apple < Fruit
  @@kind = "apple"
end

Apple.kind
# => "apple" 

Fruit.kind
# => "apple" 

เปลี่ยน kind ตัวแปรในคลาสลูกจะเปลี่ยนบนพาเรนต์ด้วย มันค่อนข้างเลอะเทอะ แต่นี่เป็นเพียงวิธีการทำงานของภาษา มันเป็นการตัดสินใจในการออกแบบมานานแล้วเพื่อเลียนแบบ Smalltalk

ยิ่งแย่ลงไปอีก

มีตัวอย่างอื่น ๆ ของความแปลกประหลาดของตัวแปรคลาสที่ดูเหมือนจะไม่ใช่ตัวเลือกทางสถาปัตยกรรมมากเท่ากับความไม่ชอบมาพากลในการใช้งาน วันนี้ผมจะมาพูดถึงเรื่องหนึ่งที่น่าสนใจบ้าง

เราจะเปรียบเทียบโค้ดสองส่วนในตอนนี้ ดูเหมือนว่าควรให้ผลลัพธ์ที่เหมือนกันแต่ไม่เป็นเช่นนั้น

ในตัวอย่างแรกของเรา เราตั้งค่าตัวแปรคลาสและสร้างวิธีการส่งคืน ไม่มีอะไรแฟนซีเกิดขึ้นที่นี่อย่างแน่นอน และทุกอย่างก็เป็นไปตามคาด

class Foo
  @@val = 1234

  # This is shorthand for declaring a class method.
  class << self
    def val
      @@val
    end
  end
end

Foo.val
# => 1234

บางทีคุณอาจไม่รู้เรื่องนี้ แต่ class << self ไม่จำเป็นต้องอยู่ในคำจำกัดความของคลาส ในตัวอย่างด้านล่าง เราได้ย้ายออกแล้ว มีการเพิ่มเมธอดของคลาสแต่ไม่สามารถเข้าถึงตัวแปรคลาสได้

class Bar
  @@val = 1234
end

class << Bar
  def val
    @@val
  end
end

Bar.val

# warning: class variable access from toplevel
# NameError: uninitialized class variable @@val in Object

เมื่อเราพยายามเข้าถึงตัวแปรคลาสจากฟังก์ชันของเรา เราจะได้รับคำเตือนและข้อยกเว้น เกิดอะไรขึ้น?

ป้อนขอบเขตคำศัพท์

ฉันมั่นใจมากขึ้นเรื่อยๆ ว่าขอบเขตคำศัพท์เป็นที่มาของ 99% ของแง่มุมที่แปลกประหลาดและน่าสับสนของ Ruby

ในกรณีที่คุณไม่คุ้นเคยกับคำศัพท์ การกำหนดขอบเขตศัพท์หมายถึงการจัดกลุ่มสิ่งต่าง ๆ เข้าด้วยกันโดยพิจารณาจากตำแหน่งที่ปรากฏในโค้ด ไม่ใช่ตำแหน่งที่อยู่ในโมเดลวัตถุนามธรรม ง่ายกว่ามากที่จะแสดงตัวอย่าง:

class B
  # x and y share the same lexical scope
  x = 1
  y = 1
end

class B
  # z has a different lexical scope from x and y, even though it's in the same class. 
  z = 3
end

คลาสถูกกำหนดแบบ Lexically

ขอบเขตคำศัพท์ในตัวอย่างตัวแปรคลาสของเราเป็นอย่างไร

ในการดึงตัวแปรคลาส Ruby ต้องรู้ว่าคลาสใดที่จะรับมัน ใช้การกำหนดขอบเขตคำศัพท์เพื่อค้นหาคลาส

หากเราดูตัวอย่างการทำงานอย่างใกล้ชิดมากขึ้น เราจะเห็นว่ารหัสที่เข้าถึงตัวแปรคลาสนั้นมีอยู่จริงในคำจำกัดความของคลาส

class Foo  
  class << self
    def val
      # I'm lexically scoped to Foo!
      @@val
    end
  end
end

ในตัวอย่างที่ไม่ทำงาน โค้ดที่เข้าถึงตัวแปรคลาสไม่ได้กำหนดขอบเขตตามคำศัพท์สำหรับคลาส

class << Bar
  def val 
    # Foo is nowhere in sight. 
    @@val
  end
end

แต่ถ้ามันไม่ได้กำหนดขอบเขตศัพท์ตามคลาส มันกำหนดขอบเขตไว้เพื่ออะไร คำเตือนที่ Ruby พิมพ์ทำให้เรามีเงื่อนงำ:warning: class variable access from toplevel .

ในตัวอย่างที่ไม่ทำงาน ปรากฎว่าตัวแปรคลาสถูกกำหนดขอบเขตตามคำศัพท์ไปยังออบเจกต์ระดับบนสุด ซึ่งอาจทำให้เกิดพฤติกรรมแปลก ๆ ได้

ตัวอย่างเช่น หากเราพยายามตั้งค่าตัวแปรคลาสจากโค้ดที่มีการกำหนดขอบเขตศัพท์เป็น main ตัวแปรคลาสจะถูกตั้งค่าเป็น Object .

class Bar
end

class << Bar
  def val=(n)
    # This code is lexically scoped to the top level object. 
    # That means, that `@@val` will be set on `Object`, not `Bar`
    @@val = i
  end
end

Bar.val = 100

# Whaa?
Object.class_variables
# => [:@@foo]

ตัวอย่างเพิ่มเติม

มีหลายวิธีในการอ้างอิงตัวแปรคลาสนอกขอบเขตคำศัพท์ของคลาส พวกเขาทั้งหมดจะทำให้คุณลำบาก

นี่คือตัวอย่างบางส่วนเพื่อความเพลิดเพลินของคุณ:

class Foo
  @@foo = :foo
end

# This won't work
Foo.class_eval { puts @@foo }

# neither will this
Foo.send :define_method, :x do 
  puts @@foo
end

# ..and you weren't thinking about using modules, were you? 
module Printable
  def foo
    puts @@foo
  end
end

class Foo
  @@foo = :foo
  include Printable
end

Foo.new.foo

และตอนนี้คุณก็รู้แล้วว่าทำไมทุกคนถึงบอกว่าอย่าใช้ตัวแปรคลาสใน Ruby :)