Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Hiểu biến class Ruby và Scope của chúng: Hướng dẫn rõ ràng

Biến class của Ruby gây nhầm lẫn. Ngay expert Ruby users cũng thấy khó trực quan. Ví dụ rõ ràng nhất liên quan đến inheritance:

class Fruit @@kind = nil def self.kind @@kind end end class Apple < Fruit @@kind = "apple" end Apple.kind # => "apple" Fruit.kind # => "apple" 

Thay đổi biến kind trên child class cũng thay đổi nó trên parent. Nó khá rối rắm. Nhưng đây chính là cách ngôn ngữ thiết kế. Là quyết định design từ lâu để mimic Smalltalk.

Nó tệ hơn

Có ví dụ khác về sự kỳ quặc của biến class không giống như lựa chọn kiến trúc mà như quirk implementation. Hôm nay tôi nói về một trong số đó mà tôi thấy thú vị.

Chúng ta sẽ so sánh hai đoạn code. Chúng trông giống như cho kết quả giống nhau nhưng không.

Trong ví dụ đầu, chúng ta set biến class và tạo method trả về nó. Không có gì fancy. Và mọi thứ hoạt động như mong đợi.

class Foo @@val = 1234 # Shorthand khai báo class method. class << self def val @@val end end end Foo.val # => 1234 

Có thể bạn không biết, nhưng class << self không phải ở bên trong class definition. Trong ví dụ dưới, chúng ta di chuyển nó ra ngoài. Class method được thêm nhưng không thể truy cập biến class.

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 

Khi thử truy cập biến class từ function, ta nhận warning và exception. Điều gì đang xảy ra?

Vào lexical scope

Tôi ngày càng tin lexical scope là nguồn gốc của 99% khía cạnh kỳ lạ và nhầm lẫn của Ruby.

Nếu bạn không quen thuật ngữ, lexical scoping là nhóm thứ lại dựa trên vị trí xuất hiện trong code, không phải dựa trên vị trí trong object model trừu tượng. Dễ hơn là show ví dụ:

class B # x và y chia sẻ cùng lexical scope x = 1 y = 1 end class B # z có lexical scope khác x và y, dù nó ở cùng class. z = 3 end 

Class được xác định bằng Lexical

Vậy lexical scope hoạt động như thế nào trong ví dụ biến class?

Để retrieve biến class, Ruby phải biết lấy từ class nào. Nó dùng lexical scoping để tìm class.

Nếu nhìn kỹ ví dụ working, code truy cập biến class nằm vật lý trong class definition.

class Foo class << self def val # Tôi lexically scoped tới Foo! @@val end end end 

Trong ví dụ non-working, code truy cập biến class KHÔNG lexically scoped tới class.

class << Bar def val # Foo không hề xuất hiện. @@val end end 

Nhưng nếu không lexically scoped tới class, nó scoped tới đâu? Warning Ruby in ra cho manh mối: warning: class variable access from toplevel.

Trong ví dụ non-working, biến class lexically scoped tới top-level object. Điều này gây hành vi kỳ lạ.

Ví dụ, nếu chúng ta thử set biến class từ code lexically scoped tới main, biến class được set trên Object.

class Bar end class << Bar def val=(n) # Code này lexically scoped tới top-level object. # Nghĩa là, `@@val` sẽ được set trên `Object`, không phải `Bar` @@val = i end end Bar.val = 100 # Whaa? Object.class_variables # => [:@@foo] 

Nhiều ví dụ hơn

Có nhiều cách reference biến class bên ngoài lexical scope của class. Tất cả gây rắc rối.

Dưới đây vài ví dụ cho vui:

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 

Và bây giờ bạn biết tại sao mọi người nói đừng bao giờ dùng biến class trong Ruby. :)