Tipe privat

Tipe privat dengan representasi tersembunyi dan operasi yang diekspor.

Daftar isi

Tipe privat (private type) adalah tipe yang namanya diekspor oleh sebuah package, sedangkan representasinya disembunyikan pada bagian privat (private part) spesifikasi. Klien hanya mengetahui bahwa tipe itu ada beserta operasi yang boleh dilakukan padanya. Topik ini membahas cara mendeklarasikan tipe privat, alasan representasinya ditempatkan pada bagian privat, dan batasan yang ditegakkan kompilator terhadap klien. Contoh yang digunakan adalah log pencatatan suhu sensor dengan kapasitas terbatas, lengkap dengan operasi pencatatan dan penghitungan rata-ratanya.

Tipe privat pada spesifikasi#

Deklarasi tipe privat terdiri atas dua bagian yang keduanya berada pada satu berkas spesifikasi. Pada bagian yang terlihat (visible part), tipe dinyatakan hanya dengan frasa type Log is private, dan deklarasi tersebut diikuti daftar operasi yang diekspor. Setelah kata kunci private, spesifikasi memuat representasi lengkap yang hanya dapat dilihat oleh badan package. Spesifikasi berikut menggunakan pola tersebut untuk tipe log suhu.

ada log_sensor.ads
package Log_Sensor is

   Jumlah_Maksimum : constant := 8;

   type Log is private;

   procedure Kosongkan (Item : out Log);
   procedure Catat (Item : in out Log; Suhu : Float);
   function Jumlah_Data (Item : Log) return Natural;
   function Rata_Rata (Item : Log) return Float;

private

   type Daftar_Suhu is array (1 .. Jumlah_Maksimum) of Float;

   type Log is record
      Isi    : Daftar_Suhu := (others => 0.0);
      Banyak : Natural     := 0;
   end record;

end Log_Sensor;

Klien melihat empat operasi, yaitu Kosongkan, Catat, Jumlah_Data, dan Rata_Rata, sedangkan record Log beserta komponen Isi dan Banyak tidak dapat diakses sama sekali. Konstanta Jumlah_Maksimum sengaja ditempatkan pada bagian yang terlihat, sehingga klien dapat membaca batas kapasitas log. Bahasa menjamin bahwa klien tidak dapat menentukan isi Log secara langsung, sehingga satu-satunya sumber nilai Log adalah operasi yang diekspor dan nilai bawaan pada bagian privat. Karena itu, setiap invariant data, misalnya jumlah data tidak boleh melebihi kapasitas, cukup dijaga di satu tempat, yaitu di dalam package.

Nilai bawaan pada bagian privat tetap berlaku meskipun klien tidak mengetahuinya. Deklarasi L : Log_Sensor.Log; pada program klien mengaktifkan nilai bawaan kedua komponen record, sehingga objek langsung siap digunakan dengan jumlah data nol. Apabila representasi berubah menjadi bentuk lain, nilai bawaan itu cukup disesuaikan di satu tempat tanpa mengubah satu baris kode klien pun. Di sinilah letak keunggulan pola tipe privat, yaitu penjagaan konsistensi awal menjadi tanggung jawab package, bukan tanggung jawab setiap klien.

Badan dan program klien#

Badan package melihat representasi lengkap karena badan dapat membaca bagian privat spesifikasinya. Implementasi berikut menyimpan suhu pada array dan menghitung rata-rata dengan perulangan sederhana.

ada log_sensor.adb
package body Log_Sensor is

   procedure Kosongkan (Item : out Log) is
   begin
      Item := (Isi => (others => 0.0), Banyak => 0);
   end Kosongkan;

   procedure Catat (Item : in out Log; Suhu : Float) is
   begin
      if Item.Banyak = Jumlah_Maksimum then
         raise Constraint_Error;
      end if;
      Item.Banyak := Item.Banyak + 1;
      Item.Isi (Item.Banyak) := Suhu;
   end Catat;

   function Jumlah_Data (Item : Log) return Natural is
   begin
      return Item.Banyak;
   end Jumlah_Data;

   function Rata_Rata (Item : Log) return Float is
      Total : Float := 0.0;
   begin
      if Item.Banyak = 0 then
         return 0.0;
      end if;
      for I in 1 .. Item.Banyak loop
         Total := Total + Item.Isi (I);
      end loop;
      return Total / Float (Item.Banyak);
   end Rata_Rata;

end Log_Sensor;

Program klien berikut mendeklarasikan objek bertipe Log_Sensor.Log, mengisinya melalui Kosongkan dan Catat, lalu membaca hasilnya melalui Jumlah_Data dan Rata_Rata. Tidak ada satu baris pun yang menyentuh komponen internal.

ada uji_log_sensor.adb
with Ada.Text_IO;
with Ada.Float_Text_IO;
with Ada.Integer_Text_IO;
with Log_Sensor;

procedure Uji_Log_Sensor is

   L : Log_Sensor.Log;

begin
   Log_Sensor.Kosongkan (L);
   Log_Sensor.Catat (L, 22.5);
   Log_Sensor.Catat (L, 23.0);
   Log_Sensor.Catat (L, 21.5);

   Ada.Text_IO.Put ("Jumlah data : ");
   Ada.Integer_Text_IO.Put (Log_Sensor.Jumlah_Data (L), Width => 1);
   Ada.Text_IO.New_Line;

   Ada.Text_IO.Put ("Rata-rata   : ");
   Ada.Float_Text_IO.Put (Log_Sensor.Rata_Rata (L), Fore => 1, Aft => 2, Exp => 0);
   Ada.Text_IO.New_Line;
end Uji_Log_Sensor;
bash
# kompilasi seluruh program dari direktori yang memuat berkas-berkasnya
gnatmake uji_log_sensor.adb
text Keluaran
gcc -c uji_log_sensor.adb
gcc -c log_sensor.adb
gnatbind -x uji_log_sensor.ali
gnatlink uji_log_sensor.ali
bash
# jalankan program hasil kompilasi
./uji_log_sensor
text Keluaran
Jumlah data : 3
Rata-rata   : 22.33

Keluaran memperlihatkan bahwa tiga data tersimpan dan rata-ratanya dihitung dengan benar. Objek L dideklarasikan tanpa inisialisasi eksplisit apa pun, kemudian diisi melalui prosedur Kosongkan. Pola inilah yang dimaksudkan oleh tipe privat, yaitu klien berinteraksi dengan perilaku, bukan dengan susunan data.

Perubahan representasi dan biayanya#

Bagian privat tetap terletak di dalam berkas spesifikasi, sehingga perubahan representasi adalah perubahan spesifikasi. Deklarasi tipe dikompilasi secara utuh bersama klien, termasuk bagian privatnya, agar legalitas kode klien dapat diperiksa. Akibatnya, gnatmake mengompilasi ulang seluruh klien ketika bagian privat berubah, walaupun tidak satu klien pun menyebut komponen tersembunyi tersebut.

bash
# simulasikan perubahan pada bagian privat spesifikasi
touch log_sensor.ads
# kompilasi ulang program
gnatmake uji_log_sensor.adb
text Keluaran
gcc -c uji_log_sensor.adb
gcc -c log_sensor.adb
gnatbind -x uji_log_sensor.ali
gnatlink uji_log_sensor.ali

Keluaran menunjukkan program klien dan badan package sama-sama dikompilasi ulang. Karena itu, representasi perlu dirancang cermat sejak awal, sedangkan implementasi operasi di badan tetap bebas diperbaiki tanpa biaya kompilasi ulang klien. Meskipun komponen Log tersembunyi, operasi bawaan bahasa tetap tersedia bagi klien. Dua objek bertipe Log dapat disalin melalui pernyataan penugasan seperti L2 := L1 dan dibandingkan dengan operator = serta /=. Apabila penyalinan itu justru tidak dikehendaki, bahasa menyediakan bentuk deklarasi yang lebih ketat.

Kesalahan umum#

Dua kesalahan berikut adalah upaya klien melanggar batas yang ditegakkan tipe privat. Pesan errornya terlihat tidak lazim karena kompilator menilai program dari sudut pandang klien, yaitu tipe tanpa komponen yang terlihat.

Kesalahan pertama adalah mengakses komponen yang berada pada bagian privat. Klien menuliskan seleksi komponen seolah-olah record itu terlihat.

ada uji_akses_error.adb
with Log_Sensor;

--  Berkas ini sengaja salah: mengakses komponen di bagian privat.
procedure Uji_Akses_Error is
   L : Log_Sensor.Log;
begin
   L.Banyak := 0;
end Uji_Akses_Error;
bash
# kompilasi berkas yang mengakses komponen privat
gnatmake uji_akses_error.adb
text Keluaran
gcc -c uji_akses_error.adb
uji_akses_error.adb:7:04: error: invalid prefix in selected component "L"
gnatmake: "uji_akses_error.adb" compilation error

Pesan error menyebut awalan seleksi tidak sah, karena dari sudut pandang klien tipe Log tidak memiliki komponen apa pun. Perbaikannya adalah memanggil operasi yang diekspor, misalnya Log_Sensor.Kosongkan (L) untuk mengosongkan data. Apabila operasi yang dibutuhkan belum ada, tambahkan deklarasi operasinya pada bagian yang terlihat, lalu implementasikan di badan.

Kesalahan kedua adalah menginisialisasi tipe privat dengan agregat record. Agregat hanya dapat ditulis oleh unit yang melihat representasinya, yaitu package itu sendiri.

ada uji_agregat_error.adb
with Log_Sensor;

--  Berkas ini sengaja salah: menginisialisasi tipe privat dengan agregat.
procedure Uji_Agregat_Error is
   L : Log_Sensor.Log := (Isi => (others => 0.0), Banyak => 0);
begin
   null;
end Uji_Agregat_Error;
bash
# kompilasi berkas yang menginisialisasi tipe privat dengan agregat
gcc -c uji_agregat_error.adb
text Keluaran
uji_agregat_error.adb:5:26: error: expected private type "Log" defined at log_sensor.ads:5
uji_agregat_error.adb:5:26: error: found a composite type

Pesan error membandingkan tipe privat yang diharapkan dengan tipe komposit yang dituliskan, karena agregat mengungkap susunan internal yang seharusnya tersembunyi. Perbaikannya adalah menghilangkan inisialisasi tersebut dan mengisi objek melalui operasi yang diekspor, misalnya Kosongkan, sebagaimana dilakukan pada program klien di atas.

Ringkasan#

  • Tipe privat mengekspor nama tipe beserta operasinya, sedangkan representasinya ditulis pada bagian privat spesifikasi yang hanya dilihat oleh badan package.
  • Klien berinteraksi melalui operasi yang diekspor, sehingga invariant data dijaga di satu tempat dan detail penyimpanan dapat berubah tanpa menyentuh kode klien.
  • Bagian privat tetap terletak di dalam berkas spesifikasi, sehingga perubahannya memicu kompilasi ulang seluruh klien walaupun tidak ada kode klien yang menyebutnya.

Referensi#