Class variables ของ Ruby สับสน แม้ expert Ruby users ก็หา intuition ยาก ตัวเด่นคือ inheritance:
class Fruit
@@kind = nil
def self.kind
@@kind
end
end
class Apple < Fruit
@@kind = "apple"
end
Apple.kind # => "apple"
Fruit.kind # => "apple"
เปลี่ยน kind variable บน child class เปลี่ยนบน parent ด้วย มัน messed up แต่เป็น design decision แบบเก่าเลียนแบบ Smalltalk
แย่กว่านี้
มี weirdness อื่นๆ ที่ไม่ใช่ architectural choices แต่ implementation quirks วันนี้พูดถึงอันที่ผมสนใจ
เปรียบเทียบสองชิ้นโค้ด ดูเหมือนควรผลลัพธ์เหมือนกัน แต่ไม่
ตัวอย่างแรก: set class variable สร้างเมธอดคืนค่า ไม่มีอะไร fancy ทุกอย่าง work ตามคาด
class Foo
@@val = 1234
# Shorthand สำหรับ declare class method
class << self
def val
@@val
end
end
end
Foo.val # => 1234
อาจไม่รู้ว่า class << self ไม่จำเป็นต้องอยู่ใน class definition ตัวอย่างย้ายออกมา class method เพิ่มแตเข้าถึง class variable ไม่ได้
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
พยายามเข้าถึง class variable จาก function ได้ warning และ exception เกิดอะไรขึ้น?
เข้าสู่ Lexical Scope
ผมเชื่อมากขึ้นว่า lexical scope เป็นต้นทาง 99% ของ weird/confusing aspects ของ Ruby
ถ้าไม่คุ้น lexical scoping: grouping things ตามที่ปรากฏในโค้ด ไใช่ตาม abstract object model ตัวอย่างง่าย:
class B
# x และ y share same lexical scope
x = 1
y = 1
end
class B
# z lexical scope ต่างจาก x และ y แม้อยู่ในคลาสเดียวกัน
z = 3
end
Class กำหนดโดย Lexically
Lexical scope ทำงานใน class variable example ยังไง?
เพื่อ retrieve class variable Ruby ต้องรู้ว่า class ไหน ใช้ lexical scoping หา class
ดูตัวอย่างที่ work โค้ดเข้าถึง class variable อยู่ physically within class definition:
class Foo
class << self
def val
# I'm lexically scoped to Foo!
@@val
end
end
end
ตัวอย่าง non-working โค้ดเข้าถึง class variable ไม่ได้ lexically scoped to the class:
class << Bar
def val
# Foo ไม่อยู่ที่ไหนเลย
@@val
end
end
แตถ้าไม่ได้ lexically scoped to the class scoped to อะไร? Warning ให้ clue: warning: class variable access from toplevel
ตัวอย่าง non-working class variable lexically scoped to top-level object อาจทำ strange behavior
เช่น set class variable จาก code lexically scoped to main class variable set บน Object:
class Bar
end
class << Bar
def val=(n)
# Code นี้ lexically scoped to top-level object
# หมายความว่า `@@val` จะ set บน `Object`, ไม่ใช่ `Bar`
@@val = n
end
end
Bar.val = 100
# Whaa?
Object.class_variables # => [:@@foo]
ตัวอย่างเพิ่มเติม
มีหลายวิธี reference class variables นอก lexical scope ของ class ทั้งหมดทำปัญหา
ตัวอย่างเพื่อความเพลิดเพลิน:
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
ตอนนี้คุณรู้ทำไมทุกคนบอกว่าอย่าใช้ class variables ใน Ruby :)