Biến thể hiện của lớp Ruby so với biến lớp


179

Tôi đọc " Khi nào các biến thể hiện của Ruby được thiết lập? " Nhưng tôi có hai ý nghĩ khi nào nên sử dụng các biến thể hiện của lớp.

Các biến lớp được chia sẻ bởi tất cả các đối tượng của một lớp, các biến Instance thuộc về một đối tượng. Không còn nhiều chỗ để sử dụng các biến thể hiện của lớp nếu chúng ta có các biến lớp.

Ai đó có thể giải thích sự khác biệt giữa hai và khi nào sử dụng chúng?

Đây là một ví dụ mã:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

Bây giờ tôi đã hiểu, Biến sơ thẩm lớp không được truyền dọc theo chuỗi thừa kế!

Câu trả lời:


276

Biến sơ thẩm trên một lớp:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

Biến lớp:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

Với một biến đối tượng trên một lớp (không phải trên một thể hiện của lớp đó), bạn có thể lưu trữ một cái gì đó chung cho lớp đó mà không có các lớp con tự động cũng có được chúng (và ngược lại). Với các biến lớp, bạn có thể không phải viết self.classtừ một đối tượng thể hiện và (khi muốn) bạn cũng có thể chia sẻ tự động trong toàn bộ phân cấp lớp.


Hợp nhất các yếu tố này lại với nhau thành một ví dụ duy nhất bao gồm các biến thể hiện trên các thể hiện:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

Và sau đó trong hành động:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 

@Phronz Sự khác biệt giữa self.things và self. Class.things bạn đã đề cập trong mã là gì?
cyborg

1
@cyborg đã self.thingstham chiếu một phương thức thingstrong phạm vi hiện tại (trong trường hợp là một thể hiện của một lớp, nó sẽ là phương thức của thể hiện), trong đó self.class.thingstham chiếu một thingsphương thức từ lớp phạm vi hiện tại (một lần nữa trong trường hợp của một lớp của nó có nghĩa là phương pháp lớp).
graffzon

Đẹp giải thích.
aliahme922

30

Tôi tin rằng chính (chỉ?) Khác nhau là thừa kế:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

Các biến lớp được chia sẻ bởi tất cả các "thể hiện lớp" (tức là các lớp con), trong khi các biến thể hiện của lớp chỉ dành riêng cho lớp đó. Nhưng nếu bạn không bao giờ có ý định mở rộng lớp học của mình, sự khác biệt hoàn toàn là học thuật.


1
Đó không phải là sự khác biệt duy nhất. "Chia sẻ" so với "ví dụ" đi xa hơn là chỉ kế thừa. Nếu bạn đặt getters cá thể, bạn sẽ nhận được S.new.s => nilS.new.k => 23.
Andre Figueiredo

27

Nguồn

Sẵn có cho các phương thức cá thể

  • Các biến thể hiện của lớp chỉ có sẵn cho các phương thức lớp và không có các phương thức cá thể.
  • Các biến lớp có sẵn cho cả phương thức cá thể và phương thức lớp.

Kế thừa

  • Các biến thể hiện của lớp bị mất trong chuỗi thừa kế.
  • Biến lớp thì không.
class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method

15

Như những người khác đã nói, các biến lớp được chia sẻ giữa một lớp nhất định và các lớp con của nó. Các biến thể hiện của lớp thuộc về chính xác một lớp; các lớp con của nó là riêng biệt.

Tại sao hành vi này tồn tại? Chà, mọi thứ trong Ruby là một đối tượng thậm chí là các lớp. Điều đó có nghĩa là mỗi lớp có một đối tượng của lớp Class(hay đúng hơn là một lớp con Class) tương ứng với nó. (Khi bạn nói class Foo, bạn thực sự tuyên bố một hằng sốFoo và gán một đối tượng lớp cho nó.) Và mọi đối tượng Ruby đều có thể có các biến thể hiện, do đó, các đối tượng lớp cũng có thể có các biến đối tượng.

Vấn đề là, các biến đối tượng trên các đối tượng lớp không thực sự hành xử theo cách bạn thường muốn các biến lớp hoạt động. Bạn thường muốn một biến lớp được định nghĩa trong một siêu lớp được chia sẻ với các lớp con của nó, nhưng đó không phải là cách các biến thể hiện hoạt động. Lớp con có đối tượng lớp riêng và đối tượng lớp đó có các biến đối tượng riêng. Vì vậy, họ đã giới thiệu các biến lớp riêng biệt với hành vi mà bạn có nhiều khả năng muốn.

Nói cách khác, các biến thể hiện của lớp là một sự cố trong thiết kế của Ruby. Bạn có thể không nên sử dụng chúng trừ khi bạn biết cụ thể chúng là thứ bạn đang tìm kiếm.


vậy biến lớp giống như biến tĩnh trong Java?
Kick Buttowski

3

Câu hỏi thường gặp về Ruby: Sự khác biệt giữa các biến lớp và biến thể hiện của lớp là gì?

Sự khác biệt chính là hành vi liên quan đến kế thừa: các biến lớp được chia sẻ giữa một lớp và tất cả các lớp con của nó, trong khi các biến thể hiện của lớp chỉ thuộc về một lớp cụ thể.

Các biến lớp theo một cách nào đó có thể được coi là các biến toàn cục trong bối cảnh của hệ thống phân cấp thừa kế, với tất cả các vấn đề đi kèm với các biến toàn cục. Ví dụ, một biến lớp có thể (vô tình) được gán lại bởi bất kỳ lớp con nào của nó, ảnh hưởng đến tất cả các lớp khác:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

Hoặc, một lớp tổ tiên sau đó có thể được mở lại và thay đổi, với những hiệu ứng đáng ngạc nhiên:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

Vì vậy, trừ khi bạn biết chính xác những gì bạn đang làm và rõ ràng cần loại hành vi này, tốt hơn là bạn nên sử dụng các biến thể hiện của lớp.


2

Đối với những người có nền tảng C ++, bạn có thể quan tâm đến việc so sánh với tương đương C ++:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

Như chúng ta có thể thấy, klà một staticbiến giống như. Đây là 100% giống như một biến toàn cục, ngoại trừ việc nó thuộc sở hữu của lớp (nằm trong phạm vi chính xác). Điều này giúp dễ dàng tránh xung đột giữa các biến được đặt tên tương tự. Giống như bất kỳ biến toàn cục nào, chỉ có một phiên bản của biến đó và sửa đổi nó luôn được hiển thị bởi tất cả.

Mặt khác, slà một giá trị cụ thể đối tượng. Mỗi đối tượng có ví dụ riêng của giá trị. Trong C ++, bạn phải tạo một thể hiện để có quyền truy cập vào biến đó. Trong Ruby, định nghĩa lớp tự nó là một thể hiện của lớp (trong JavaScript, đây được gọi là nguyên mẫu), do đó bạn có thể truy cập stừ lớp mà không cần thêm thời gian. Thể hiện của lớp có thể được sửa đổi, nhưng sửa đổi ssẽ là cụ thể cho từng thể hiện (từng đối tượng của loại S). Vì vậy, sửa đổi cái này sẽ không thay đổi giá trị trong cái khác.


1

Mặc dù có thể ngay lập tức có vẻ hữu ích khi sử dụng các biến thể hiện của lớp, vì biến thể hiện của lớp được chia sẻ giữa các lớp con và chúng có thể được tham chiếu trong cả hai phương thức cá thể và cá thể, có một nhược điểm đáng kể. Chúng được chia sẻ và vì vậy các lớp con có thể thay đổi giá trị của biến thể hiện của lớp và lớp cơ sở cũng sẽ bị ảnh hưởng bởi sự thay đổi, thường là hành vi không mong muốn:

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Rails giới thiệu một phương thức tiện dụng gọi là class_attribution. Như tên của nó, nó khai báo một thuộc tính cấp lớp có giá trị được kế thừa bởi các lớp con. Giá trị class_attribution có thể được truy cập trong cả hai phương thức singleton và cá thể, như trường hợp với biến thể hiện của lớp. Tuy nhiên, lợi ích to lớn với class_attribution trong Rails là các lớp con có thể thay đổi giá trị của riêng chúng và nó sẽ không ảnh hưởng đến lớp cha.

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 

Cuộc gọi tốt, tôi đã không sử dụng điều này trước đây. Nó dường như hoạt động mặc dù bạn cần chắc chắn trả trước self.mỗi khi bạn muốn truy cập thuộc tính c, ví dụ self.c. Các tài liệu nói rằng một default:tham số có thể được truyền vào class_attributenhưng nó dường như không hoạt động do điểm tôi vừa đề cập self.
Dex

Khi bạn nói "Mặc dù có thể ngay lập tức có ích khi sử dụng các biến thể hiện của lớp", tôi nghĩ bạn có nghĩa là "biến lớp", chứ không phải "biến thể hiện của lớp phải không? (Xem ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett

Có, câu trả lời này hoàn toàn nhầm lẫn "biến thể hiện của lớp" và "biến lớp", đó là toàn bộ điểm của câu hỏi.
stevo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.