ตัวแปรอินสแตนซ์คลาส Ruby เทียบกับตัวแปรคลาส


179

ฉันอ่าน " ตั้งค่าตัวแปรอินสแตนซ์ของ Ruby เมื่อใด " แต่ฉันเป็นคนสองคนที่จะใช้ตัวแปรอินสแตนซ์คลาส

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

ใครสามารถอธิบายความแตกต่างระหว่างสองสิ่งนี้กับเวลาที่ใช้

นี่คือตัวอย่างรหัส:

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

ฉันเข้าใจแล้วในตอนนี้ตัวแปรของอินสแตนซ์ของคลาสไม่ได้ถูกส่งผ่านห่วงโซ่การสืบทอด!

คำตอบ:


276

ตัวแปรอินสแตนซ์บนคลาส:

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]

ตัวแปรคลาส:

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]

ด้วยตัวแปรอินสแตนซ์ในคลาส (ไม่ใช่อินสแตนซ์ของคลาสนั้น) คุณสามารถจัดเก็บบางสิ่งที่เหมือนกันของคลาสนั้นโดยไม่ต้องมีคลาสย่อยโดยอัตโนมัติเช่นกัน ด้วยตัวแปรคลาสคุณมีความสะดวกในการไม่ต้องเขียนself.classจากวัตถุอินสแตนซ์และ (เมื่อต้องการ) คุณยังได้รับการแบ่งปันอัตโนมัติตลอดทั้งลำดับชั้นของคลาส


ผสานสิ่งเหล่านี้เข้าด้วยกันเป็นตัวอย่างเดียวที่ครอบคลุมตัวแปรอินสแตนซ์บนอินสแตนซ์:

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

แล้วในการดำเนินการ:

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 ข้อแตกต่างระหว่าง self.things และ self.class.things ที่คุณพูดถึงในโค้ดคืออะไร?
หุ่นยนต์

1
@cyborg self.thingsอ้างถึงวิธีการthingsในขอบเขตปัจจุบัน (ในกรณีของอินสแตนซ์ของคลาสมันจะเป็นวิธีการของอินสแตนซ์) ซึ่งself.class.thingsการอ้างอิงthingsวิธีการจากคลาสของขอบเขตปัจจุบัน (อีกครั้งในกรณีของอินสแตนซ์ของชั้นมันจะหมายถึง วิธีการเรียน)
graffzon

คำอธิบายที่สวยงาม
aliahme922

30

ฉันเชื่อว่าหลัก (เท่านั้น) ที่แตกต่างกันคือมรดก:

class T < S
end

p T.k
=> 23

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

p T.s
=> nil

ตัวแปรคลาสจะถูกแชร์โดย "อินสแตนซ์ของคลาส" ทั้งหมด (เช่นคลาสย่อย) ในขณะที่ตัวแปรอินสแตนซ์ของคลาสนั้นมีความเฉพาะกับคลาสนั้นเท่านั้น แต่ถ้าคุณไม่ตั้งใจที่จะขยายชั้นเรียนของคุณความแตกต่างคือการศึกษาอย่างแท้จริง


1
นั่นไม่ใช่ความแตกต่างเท่านั้น อินสแตนซ์ "ที่ใช้ร่วมกัน" vs "เป็นมากกว่าแค่การสืบทอด หากคุณใส่ getters เช่นคุณจะได้รับและS.new.s => nil S.new.k => 23
Andre Figueiredo

27

แหล่ง

ความพร้อมใช้งานกับวิธีการอินสแตนซ์

  • ตัวแปรอินสแตนซ์ของคลาสจะใช้ได้เฉพาะกับเมธอดของคลาสเท่านั้นและไม่ใช่กับวิธีของอินสแตนซ์
  • ตัวแปรคลาสพร้อมใช้งานกับทั้งอินสแตนซ์เมธอดและเมธอดคลาส

Inheritability

  • ตัวแปรอินสแตนซ์ของคลาสสูญหายไปในห่วงโซ่การสืบทอด
  • ตัวแปรคลาสไม่ได้
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

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

เหตุใดพฤติกรรมนี้จึงมีอยู่ ทุกอย่างในทับทิมเป็นวัตถุแม้แต่คลาส นั่นหมายความว่าแต่ละคลาสมีวัตถุของคลาสClass(หรือค่อนข้างคลาสย่อยของClass) ที่สอดคล้องกับมัน (เมื่อคุณพูดว่าclass Fooคุณกำลังประกาศค่าคงที่Fooและกำหนดคลาสวัตถุให้กับมัน) และวัตถุ Ruby ทุกตัวสามารถมีตัวแปรอินสแตนซ์ได้ดังนั้นวัตถุคลาสสามารถมีตัวแปรอินสแตนซ์ได้เช่นกัน

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

กล่าวอีกนัยหนึ่งตัวแปรอินสแตนซ์ของชั้นเรียนเป็นอุบัติเหตุจากการออกแบบของรูบี้ คุณอาจไม่ควรใช้มันหากคุณไม่ทราบว่าเป็นสิ่งที่คุณต้องการโดยเฉพาะ


ดังนั้นคลาสตัวแปรจึงเป็นเหมือนตัวแปรสแตติกใน Java?
Kick Buttowski

3

Ruby FAQ อย่างเป็นทางการ: อะไรคือความแตกต่างระหว่างตัวแปรคลาสและตัวแปรอินสแตนซ์ของคลาส?

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

ตัวแปรคลาสในบางวิธีสามารถถูกมองว่าเป็นตัวแปรโกลบอลภายในบริบทของลำดับชั้นการสืบทอดพร้อมกับปัญหาทั้งหมดที่มาพร้อมกับตัวแปรโกลบอล ตัวอย่างเช่นตัวแปรคลาสอาจถูกกำหนดโดยคลาสย่อยใด ๆ (โดยไม่ตั้งใจ) ซึ่งมีผลต่อคลาสอื่นทั้งหมด:

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" (!)

หรือชั้นบรรพบุรุษอาจเปิดใหม่และเปลี่ยนแปลงในภายหลังโดยมีเอฟเฟกต์ที่น่าประหลาดใจ:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

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

class Object
  @@var = "object"
end

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

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


2

สำหรับผู้ที่มีพื้นหลัง C ++ คุณอาจสนใจเปรียบเทียบกับ 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";

อย่างที่เราเห็นkเป็นstaticตัวแปรที่คล้ายกัน นี่คือ 100% เหมือนตัวแปรทั่วโลกยกเว้นว่าเป็นของคลาส ( กำหนดขอบเขตให้ถูกต้อง) สิ่งนี้ทำให้ง่ายขึ้นในการหลีกเลี่ยงการปะทะกันระหว่างตัวแปรที่มีชื่อคล้ายกัน เช่นเดียวกับตัวแปรอื่น ๆ ทั่วโลกมีเพียงหนึ่งอินสแตนซ์ของตัวแปรนั้นและการแก้ไขให้ทุกคนสามารถมองเห็นได้

ในทางตรงกันข้ามsเป็นค่าเฉพาะวัตถุ แต่ละวัตถุมีตัวอย่างของค่า ใน C ++ คุณต้องสร้างอินสแตนซ์เพื่อให้สามารถเข้าถึงตัวแปรนั้นได้ ใน Ruby คำจำกัดความของคลาสนั้นเป็นอินสแตนซ์ของคลาส (ใน JavaScript ซึ่งเรียกว่าต้นแบบ) ดังนั้นคุณสามารถเข้าถึงได้sจากคลาสโดยไม่มีการสร้างอินสแตนซ์เพิ่มเติม อินสแตนซ์ของคลาสสามารถปรับเปลี่ยนได้ แต่การปรับเปลี่ยนsจะเฉพาะเจาะจงสำหรับแต่ละอินสแตนซ์ (แต่ละประเภทของอ็อบเจ็กต์S) ดังนั้นการแก้ไขอย่างใดอย่างหนึ่งจะไม่เปลี่ยนค่าในอีก


1

ในขณะที่มันอาจดูเหมือนมีประโยชน์ในการใช้ตัวแปรอินสแตนซ์ของคลาสทันทีเนื่องจากตัวแปรอินสแตนซ์ของคลาสนั้นถูกใช้ร่วมกันระหว่างคลาสย่อยและพวกมันสามารถอ้างถึงได้ทั้งในซิงเกิลตันและเมธอดอินสแตนซ์ พวกมันถูกแชร์และ subclasses สามารถเปลี่ยนค่าของตัวแปรอินสแตนซ์ของคลาสและคลาสฐานจะได้รับผลกระทบจากการเปลี่ยนแปลงซึ่งโดยปกติจะเป็นพฤติกรรมที่ไม่พึงประสงค์:

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 แนะนำวิธีการที่สะดวกสบายที่เรียกว่า class_attribute เป็นชื่อที่มีความหมายมันประกาศแอตทริบิวต์ระดับระดับที่มีค่าที่สืบทอดได้โดย subclasses ค่า class_attribute สามารถเข้าถึงได้ทั้งในซิงเกิลตันและเมธอดอินสแตนซ์ดังเช่นกรณีที่มีตัวแปรอินสแตนซ์ของคลาส อย่างไรก็ตามข้อดีอย่างมากของ class_attribute ใน Rails คือ subclasses สามารถเปลี่ยนค่าของตัวเองและจะไม่ส่งผลกระทบต่อคลาส parent

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

 C.c
 => "c" 

class D < C
end

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

 C.c
 => "c" 

เยี่ยมมากฉันไม่เคยใช้มันมาก่อน มันดูเหมือนว่าจะทำงานแม้ว่าคุณจะต้องแน่ใจว่าจะย่อหน้าself.ทุกเวลาที่คุณต้องการในการเข้าถึงแอตทริบิวต์เช่นc self.cเอกสารบอกว่าdefault:พารามิเตอร์สามารถส่งผ่านไปแต่ก็ดูเหมือนจะไม่ทำงานเนื่องจากจุดที่ผมกล่าวถึงเพียงกับclass_attribute self
Dex

เมื่อคุณพูดว่า "แม้ว่ามันอาจจะมีประโยชน์ในการใช้ตัวแปรอินสแตนซ์ของคลาส" ในทันทีฉันคิดว่าคุณหมายถึง "ตัวแปรคลาส" ไม่ใช่ "ตัวแปรอินสแตนซ์ของคลาสใช่มั้ย (ดูที่ ruby-lang.org/en/documentation/faq/8/ )
Keith Bennett

ใช่คำตอบนี้ทำให้เกิดความสับสน "ตัวแปรอินสแตนซ์ของคลาส" และ "ตัวแปรคลาส" ซึ่งเป็นจุดรวมของคำถาม
stevo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.