ทำไมต้องใช้ ruby ​​attr_accessor, attr_reader และ attr_writer


517

Ruby มีวิธีที่สะดวกและสะดวกในการแบ่งปันตัวแปรอินสแตนซ์โดยใช้ปุ่มเช่น

attr_accessor :var
attr_reader :var
attr_writer :var

ทำไมฉันถึงเลือกattr_readerหรือattr_writerถ้าฉันสามารถใช้attr_accessor? มีบางอย่างที่เหมือนกับการแสดง (ซึ่งฉันสงสัย) ฉันคิดว่ามันมีเหตุผลมิฉะนั้นพวกเขาจะไม่ทำกุญแจแบบนั้น


1
มีความเป็นไปได้ที่ซ้ำกันของattr_accessor ใน Ruby คืออะไร
sschuberth

คำตอบ:


746

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

class Person
  attr_accessor :age
  ...
end

ที่นี่ฉันเห็นว่าฉันทั้งสองสามารถอ่านและเขียนอายุได้

class Person
  attr_reader :age
  ...
end

ที่นี่ฉันเห็นว่าฉันสามารถอ่านอายุเท่านั้น ลองนึกภาพว่ามันถูกกำหนดโดยนวกรรมิกของคลาสนี้และหลังจากนั้นก็คงที่ หากมี mutator (ผู้เขียน) สำหรับอายุและคลาสนั้นถูกเขียนขึ้นโดยสมมติว่าอายุนั้นเมื่อตั้งค่าแล้วจะไม่มีการเปลี่ยนแปลงข้อผิดพลาดอาจเกิดขึ้นจากการเรียกรหัสของ mutator นั้น

แต่เกิดอะไรขึ้นเบื้องหลัง?

ถ้าคุณเขียน:

attr_writer :age

ที่ได้รับการแปลเป็น:

def age=(value)
  @age = value
end

ถ้าคุณเขียน:

attr_reader :age

ที่ได้รับการแปลเป็น:

def age
  @age
end

ถ้าคุณเขียน:

attr_accessor :age

ที่ได้รับการแปลเป็น:

def age=(value)
  @age = value
end

def age
  @age
end

รู้ว่านี่เป็นอีกวิธีหนึ่งในการคิดเกี่ยวกับสิ่งนี้: ถ้าคุณไม่มีผู้ช่วยเหลือ attr _... และต้องเขียน accessors ด้วยตัวเองคุณจะเขียน accessors ใด ๆ เกินกว่าที่ห้องเรียนต้องการหรือไม่ ตัวอย่างเช่นหากอายุเท่านั้นที่ต้องอ่านคุณจะเขียนวิธีที่อนุญาตให้เขียนได้หรือไม่


53
นอกจากนี้ยังมีข้อได้เปรียบด้านประสิทธิภาพที่สำคัญในการเขียนattr_reader :aเทียบกับdef a; return a; end confreaks.net/videos/…
Nitrodist

83
@Nitrodist ที่น่าสนใจ สำหรับ Ruby 1.8.7 attr_readeraccesor ที่กำหนดใช้ 86% ของเวลาที่ accessor ที่กำหนดด้วยตนเองทำ สำหรับ Ruby 1.9.0 ตัวattr_readeraccessor ที่กำหนดนั้นใช้เวลา 94% ของเวลาที่ accessor ที่กำหนดด้วยตนเองทำ อย่างไรก็ตามในการทดสอบทั้งหมดของฉัน accessors นั้นเร็ว: accessor ใช้เวลาประมาณ 820 nanoseconds (Ruby 1.8.7) หรือ 440 nanoseconds (Ruby 1.9) ด้วยความเร็วเหล่านั้นคุณจะต้องเรียกใช้ accessor หลายร้อยล้านครั้งเพื่อให้ได้ประสิทธิภาพattr_accessorการทำงานที่ดีขึ้นโดยรวมของรันไทม์ภายในหนึ่งวินาที
Wayne Conrad

22
"สันนิษฐานว่ามันถูกกำหนดโดยตัวสร้างของคลาสนี้และคงที่" นั่นไม่ถูกต้อง ตัวแปรอินสแตนซ์ที่มีตัวอ่านอาจเปลี่ยนแปลงบ่อยครั้ง อย่างไรก็ตามมันมีจุดมุ่งหมายที่ค่าของพวกเขาจะถูกเปลี่ยนเป็นการส่วนตัวโดยชั้นเรียนเท่านั้น
mlibby

11
คุณสามารถใช้ "," เพื่อเพิ่มแอตทริบิวต์ได้มากกว่า 2 รายการเช่น:attr_accessor :a, :b
Andrew_1510

2
สำหรับสิ่งที่มีค่าหลังจากปีเหล่านี้: github.com/JuanitoFatas/ …ตามมาตรฐานล่าสุดของ ruby ​​2.2.0 attr_ * นั้นเร็วกว่าตัวรับและ setters
molli

25

คำตอบทั้งหมดข้างต้นถูกต้อง attr_readerและattr_writerสะดวกในการเขียนมากกว่าการพิมพ์วิธีที่พวกเขาจดชวเลข นอกเหนือจากนั้นพวกเขายังให้ประสิทธิภาพที่ดีกว่าการเขียนนิยามวิธีการด้วยตัวคุณเอง สำหรับข้อมูลเพิ่มเติมดูสไลด์ 152 เป็นต้นไปจากการพูดคุยนี้ ( PDF ) โดย Aaron Patterson


16

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

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


16

สิ่งสำคัญคือต้องเข้าใจว่า accessors จำกัด การเข้าถึงตัวแปร แต่ไม่ใช่เนื้อหา ในทับทิมเช่นเดียวกับในบางภาษา OO อื่น ๆ ทุกตัวแปรเป็นตัวชี้ไปยังอินสแตนซ์ ดังนั้นหากคุณมีแอททริบิวเป็นแฮชและคุณตั้งเป็น "อ่านอย่างเดียว" คุณสามารถเปลี่ยนเนื้อหาได้ตลอด แต่ไม่สามารถเปลี่ยนเนื้อหาของตัวชี้ได้ ดูนี่สิ:

irb(main):024:0> class A
irb(main):025:1> attr_reader :a
irb(main):026:1> def initialize
irb(main):027:2> @a = {a:1, b:2}
irb(main):028:2> end
irb(main):029:1> end
=> :initialize
irb(main):030:0> a = A.new
=> #<A:0x007ffc5a10fe88 @a={:a=>1, :b=>2}>
irb(main):031:0> a.a
=> {:a=>1, :b=>2}
irb(main):032:0> a.a.delete(:b)
=> 2
irb(main):033:0> a.a
=> {:a=>1}
irb(main):034:0> a.a = {}
NoMethodError: undefined method `a=' for #<A:0x007ffc5a10fe88 @a={:a=>1}>
        from (irb):34
        from /usr/local/bin/irb:11:in `<main>'

อย่างที่คุณเห็นมีความเป็นไปได้ที่จะลบคู่คีย์ / ค่าออกจาก Hash @a เช่นเพิ่มคีย์ใหม่เปลี่ยนค่า eccetera แต่คุณไม่สามารถชี้ไปที่วัตถุใหม่เพราะเป็นตัวแปรอินสแตนซ์แบบอ่านอย่างเดียว


13

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

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