Atribut pada kelas dapat dibuka untuk dibaca dan diubah, tetapi tidak semua data sebaiknya bebas disentuh dari luar objek. Topik ini membahas attr_reader dan attr_writer sebagai pembangkit metode akses, penulisan getter dan setter manual, penyembunyian metode dengan private dan protected, serta alasan enkapsulasi (encapsulation) di balik pengaturan akses tersebut. Setelah membacanya, permukaan akses sebuah kelas dapat dirancang secara sadar dan alasan setiap batasannya dapat dijelaskan.
attr_reader dan attr_writer#
Deklarasi attr_* di kelas menghasilkan metode akses untuk variabel instans dengan nama yang sama. attr_reader :x menghasilkan metode x yang membaca @x, attr_writer :x menghasilkan metode x= yang mengubahnya, sedangkan attr_accessor :x menghasilkan keduanya sekaligus. Kelas yang hanya mengizinkan pembacaan cukup menggunakan attr_reader, dan percobaan penulisan dari luar akan gagal karena metode tulisnya memang tidak ada.
Program berikut mendefinisikan kelas SensorSuhu dengan attr_reader :celsius, kemudian membaca nilainya dan mencoba mengubahnya dari luar.
# Kelas SensorSuhu dengan attr_reader dan percobaan penulisan.
class SensorSuhu
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def dalam_fahrenheit
celsius * 9.0 / 5 + 32
end
end
sensor = SensorSuhu.new(30)
p sensor.celsius
p sensor.dalam_fahrenheit
begin
sensor.celsius = 35
rescue NoMethodError => e
puts e.class
puts e.message
end30
86.0
NoMethodError
undefined method 'celsius=' for an instance of SensorSuhuPembacaan sensor.celsius berjalan normal, sedangkan sensor.celsius = 35 memunculkan NoMethodError berpesan undefined method 'celsius='. Pesan itu tidak menyatakan metode tulisnya disembunyikan, melainkan menegaskan bahwa metode tersebut memang tidak pernah dibuat karena yang dideklarasikan hanya attr_reader. Ringkasan ketiga deklarasi disajikan pada tabel berikut.
| Deklarasi | Metode baca | Metode tulis |
|---|---|---|
attr_reader :x | x | tidak dihasilkan |
attr_writer :x | tidak dihasilkan | x= |
attr_accessor :x | x | x= |
Pilihan deklarasi disesuaikan dengan peran atributnya. Data yang ditetapkan saat pembuatan dan hanya boleh dibaca sesudahnya, seperti celsius pada contoh, cocok dengan attr_reader, sedangkan attr_writer jarang digunakan sendirian karena nilainya dapat diubah tanpa pernah terbaca. attr_accessor menjadi pilihan untuk atribut yang bebas dibaca dan diubah, dengan konsekuensi hilangnya kendali atas keabsahan nilainya.
Getter dan setter manual#
Deklarasi attr_* hanyalah bentuk ringkas, dan metode yang dihasilkannya dapat ditulis secara manual dengan def. Bentuk manual ini penting diketahui karena menjadi dasar ketika akses atribut perlu diberi logika tambahan. Dua metode berikut menghasilkan perilaku yang sama persis dengan attr_accessor :celsius.
class SensorSuhu
def initialize(celsius)
@celsius = celsius
end
def celsius
@celsius
end
def celsius=(nilai)
@celsius = nilai
end
end
sensor = SensorSuhu.new(30)
p sensor.celsius
sensor.celsius = 28
p sensor.celsius30
28Metode bernama celsius disebut getter karena membaca nilai, sedangkan metode bernama celsius= disebut setter karena mengubahnya. Nama metode yang diakhiri tanda sama dengan memungkinkan pemanggilan dalam bentuk penugasan sensor.celsius = 28, yang setara dengan sensor.celsius=(28). Dengan bentuk manual, baris-baris pemeriksaan bebas ditambahkan di dalam setter sebelum penugasan dilakukan, dan itulah yang tidak mungkin dilakukan oleh attr_writer.
Kedua bentuk ini dapat digunakan berdampingan. Deklarasi attr_reader :celsius tetap digunakan untuk metode baca, sedangkan metode celsius= ditulis manual hanya ketika penulisannya perlu diberi aturan. Kelas yang tumbuh dari prototipe sederhana biasanya mengikuti pola ini, yaitu deklarasi ringkas terlebih dahulu, lalu setter manual menggantikan metode tulisnya pada saat aturan bisnisnya muncul.
Menyembunyikan metode dengan private#
Kata kunci private menyembunyikan metode-metode yang ditulis setelahnya dari pemanggilan dengan penerima. Metode semacam ini hanya dapat dipanggil dari dalam kelas yang sama, tanpa menyebut penerima, sehingga fungsinya terbatas sebagai perkakas internal. Pola yang lazim adalah menaruh metode publik di bagian atas kelas, lalu menaruh helper, yaitu metode pembantu internal, setelah private.
# Metode private pada kelas SensorSuhu dan percobaan panggilan dari luar.
class SensorSuhu
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def dalam_fahrenheit
format_angka(celsius * 9.0 / 5 + 32)
end
private
def format_angka(nilai)
format("%.1f", nilai)
end
end
sensor = SensorSuhu.new(30)
puts sensor.dalam_fahrenheit
begin
sensor.format_angka(21.5)
rescue NoMethodError => e
puts e.class
puts e.message
end86.0
NoMethodError
private method 'format_angka' called for an instance of SensorSuhuMetode dalam_fahrenheit memanggil format_angka dari dalam kelas tanpa penerima, sehingga panggilan itu sah dan keluarannya terformat satu angka di belakang koma. Panggilan sensor.format_angka dari luar ditolak dengan NoMethodError berpesan private method. Dengan demikian, cara pemformatan internal dapat diubah bebas tanpa risiko merusak pemanggil di luar kelas.
Penyembunyian semacam ini menjaga dua hal sekaligus. Daftar metode yang perlu dipelajari pengguna kelas tetap pendek karena hanya metode publik yang menjadi antarmukanya, sedangkan kebebasan mengubah isi kelas tetap terjaga karena helper tidak boleh disentuh dari luar. Kelas yang helper-nya bocor ke publik sulit dirapikan, sebab setiap pemanggil luar bisa saja bergantung padanya.
protected dan akses antarinstans#
protected menempati posisi tengah antara publik dan privat. Metode protected dapat dipanggil dengan penerima eksplisit selama penerimanya sesama instans dari kelas yang sama, dan tetap tertutup bagi pihak luar. Syarat ini tepat untuk operasi yang membandingkan dua objek sejenis, karena keduanya sama-sama membutuhkan akses ke data internal masing-masing.
class SensorSuhu
def initialize(celsius)
@celsius = celsius
end
def lebih_panas_dari?(lain)
celsius > lain.celsius
end
protected
def celsius
@celsius
end
end
dalam = SensorSuhu.new(30)
luar = SensorSuhu.new(26)
p dalam.lebih_panas_dari?(luar)
begin
luar.celsius
rescue NoMethodError => e
puts e.class
puts e.message
endtrue
NoMethodError
protected method 'celsius' called for an instance of SensorSuhuDi dalam lebih_panas_dari?, ekspresi lain.celsius memanggil metode protected dengan penerima eksplisit, dan panggilan itu diterima karena lain juga merupakan instans SensorSuhu. Panggilan luar.celsius dari luar kelas ditolak dengan NoMethodError berpesan protected method, meskipun metode celsius sama sekali tidak ditandai private. Perbedaan pesan tersebut menandai dua tingkat penyembunyian ini.
Kontras dengan private terlihat apabila desain yang sama diterapkan. Apabila celsius pada contoh ini ditandai private, pemanggilan lain.celsius di dalam lebih_panas_dari? ikut ditolak karena menyertakan penerima eksplisit, sehingga pembandingnya tidak dapat ditulis. protected mengisi kebutuhan di antara keduanya dengan tepat, yaitu akses antarobjek sekelas yang tetap tertutup bagi pihak luar, dan pembanding antarinstans adalah penggunaannya yang paling khas.
Alasan enkapsulasi#
Enkapsulasi adalah praktik menyembunyikan detail internal objek dan menyediakan antarmuka yang terkendali. Manfaat pertamanya adalah menjaga keabsahan data, yaitu nilai yang disimpan selalu memenuhi aturan kelas tersebut. Manfaat kedua adalah kebebasan mengubah isi kelas, karena selama antarmuka publiknya tetap, detail internal boleh ditulis ulang tanpa merusak pemanggil.
Bentuk paling nyata dari manfaat pertama adalah setter yang memeriksa nilainya sebelum menyimpan. Pada contoh berikut, suhu di bawah nol mutlak ditolak karena tidak berarti secara fisika.
class SensorSuhu
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def celsius=(nilai)
if nilai < -273.15
raise ArgumentError, "suhu tidak boleh di bawah nol mutlak"
end
@celsius = nilai
end
end
sensor = SensorSuhu.new(25)
sensor.celsius = 30
p sensor.celsius
begin
sensor.celsius = -300
rescue ArgumentError => e
puts e.class
puts e.message
end30
ArgumentError
suhu tidak boleh di bawah nol mutlakPenugasan yang sah tersimpan, sedangkan sensor.celsius = -300 memunculkan ArgumentError yang ditangkap dan dicetak. Karena semua perubahan nilai wajib melewati setter yang sama, aturan pemeriksaannya tidak dapat dilewati dari luar. Perpaduan attr_reader untuk pembacaan dan setter manual untuk penulisan adalah pola enkapsulasi yang paling sering digunakan pada kelas dengan data yang dijaga.
Manfaat kedua tampak pada pemeliharaan. Metode format_angka pada bagian private boleh diganti formatnya, dihapus, atau dipecah tanpa memengaruhi satu pun pemanggil di luar kelas, karena yang diperlihatkan ke luar hanya antarmuka publiknya. Kelas yang permukaan aksesnya kecil lebih mudah diuji, dibaca, dan diperbaiki, karena jumlah jalur menuju datanya terbatas dan terdokumentasi dengan jelas.
Ringkasan#
attr_reader,attr_writer, danattr_accessormenghasilkan metode baca, tulis, atau keduanya untuk variabel instans dengan nama yang sama.- Getter dan setter manual ditulis dengan
def, dan metode bernamax=dipanggil dalam bentuk penugasanobjek.x = nilai. privatemenyembunyikan metode dari pemanggilan dengan penerima, sedangkanprotectedmengizinkannya apabila penerimanya sesama instans kelas yang sama.- Enkapsulasi menjaga keabsahan data dengan memusatkan seluruh perubahan pada setter yang memeriksa nilainya.
Referensi#
- Module#attr_reader — Dokumentasi Ruby 3.4 — Dokumentasi attr_reader beserta keluarga attr_writer dan attr_accessor.
- Module#private — Dokumentasi Ruby 3.4 — Dokumentasi private yang mengatur jangkauan pemanggilan metode di dalam kelas.