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.
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.
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.
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;# kompilasi seluruh program dari direktori yang memuat berkas-berkasnya
gnatmake uji_log_sensor.adbgcc -c uji_log_sensor.adb
gcc -c log_sensor.adb
gnatbind -x uji_log_sensor.ali
gnatlink uji_log_sensor.ali# jalankan program hasil kompilasi
./uji_log_sensorJumlah data : 3
Rata-rata : 22.33Keluaran 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.
# simulasikan perubahan pada bagian privat spesifikasi
touch log_sensor.ads
# kompilasi ulang program
gnatmake uji_log_sensor.adbgcc -c uji_log_sensor.adb
gcc -c log_sensor.adb
gnatbind -x uji_log_sensor.ali
gnatlink uji_log_sensor.aliKeluaran 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.
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;# kompilasi berkas yang mengakses komponen privat
gnatmake uji_akses_error.adbgcc -c uji_akses_error.adb
uji_akses_error.adb:7:04: error: invalid prefix in selected component "L"
gnatmake: "uji_akses_error.adb" compilation errorPesan 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.
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;# kompilasi berkas yang menginisialisasi tipe privat dengan agregat
gcc -c uji_agregat_error.adbuji_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 typePesan 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#
- Ada Reference Manual 2022, Bagian 7.3 Private Types and Private Extensions — Ada Resource Association — Bagian ini menetapkan aturan tipe privat, termasuk pandangan klien dan pembagian bagian yang terlihat dengan bagian privat.
- Introduction to Ada — AdaCore — Bab Privacy membahas tipe privat, bagian privat, serta kaitannya dengan unit anak.