Getter/setter dan privasi

attr_reader dan attr_writer, metode private dan protected, serta alasan enkapsulasi.

Daftar isi

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.

ruby sensor-suhu.rb
# 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
end
text Keluaran
30
86.0
NoMethodError
undefined method 'celsius=' for an instance of SensorSuhu

Pembacaan 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.

DeklarasiMetode bacaMetode tulis
attr_reader :xxtidak dihasilkan
attr_writer :xtidak dihasilkanx=
attr_accessor :xxx=

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.

ruby Bentuk manual dari attr_reader dan attr_writer
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.celsius
text Keluaran
30
28

Metode 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.

ruby sensor-privat.rb
# 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
end
text Keluaran
86.0
NoMethodError
private method 'format_angka' called for an instance of SensorSuhu

Metode 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.

ruby protected pada pembanding antarinstans
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
end
text Keluaran
true
NoMethodError
protected method 'celsius' called for an instance of SensorSuhu

Di 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.

ruby Setter dengan pemeriksaan nilai
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
end
text Keluaran
30
ArgumentError
suhu tidak boleh di bawah nol mutlak

Penugasan 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, dan attr_accessor menghasilkan metode baca, tulis, atau keduanya untuk variabel instans dengan nama yang sama.
  • Getter dan setter manual ditulis dengan def, dan metode bernama x= dipanggil dalam bentuk penugasan objek.x = nilai.
  • private menyembunyikan metode dari pemanggilan dengan penerima, sedangkan protected mengizinkannya apabila penerimanya sesama instans kelas yang sama.
  • Enkapsulasi menjaga keabsahan data dengan memusatkan seluruh perubahan pada setter yang memeriksa nilainya.

Referensi#