Topik ini membahas pengaturan akses pada anggota F# dan mekanisme pewarisan antar kelas. Pembahasannya mencakup tiga modifier akses pada member dan fungsi tingkat modul, pesan error asli yang muncul ketika akses ditolak compiler, pewarisan dengan inherit dan override, serta kelas abstrak yang diberi atribut [<AbstractClass>]. Setelah topik ini dibaca, batas visibilitas sebuah pustaka F# dapat dirancang dengan sengaja dan diverifikasi langsung dari pesan compiler.
Tiga tingkat akses pada anggota#
Anggota F# bersifat publik secara bawaan. Kata kunci private membatasi akses ke tipe atau modul yang menyatakannya, sedangkan internal membatasi akses ke satu assembly, yaitu hasil kompilasi satu proyek. Pengaturan yang sama berlaku pada fungsi tingkat modul, dan modul itu sendiri pun boleh dinyatakan module private agar tidak terlihat dari berkas lain. Ringkasannya seperti tabel berikut.
| Modifier | Batas akses |
|---|---|
public | semua kode, berlaku secara bawaan |
private | tipe atau modul yang menyatakannya |
internal | seluruh kode dalam satu assembly |
Modifier juga dapat diterapkan pada tingkat yang lebih dalam maupun lebih luas. Pada kelas, binding let otomatis privat sehingga kata kuncinya tidak diperlukan, sedangkan member dan fungsi tingkat modul menuntut modifier yang dituliskan secara eksplisit. Deklarasi module private atau module internal mengatur visibilitas seluruh isi modulnya sekaligus. Contoh berikut menggabungkan ketiga tingkat tersebut pada satu modul bantu dan satu kelas arsip.
// Tiga tingkat akses pada member dan fungsi tingkat modul
module CatatanBantu =
// fungsi privat: hanya terlihat di dalam modul ini
let private rapikan(teks: string) =
teks.Trim().ToLower()
// fungsi internal: terlihat di seluruh assembly
let internal CariKataKunci(teks: string) =
rapikan teks
type ArsipCatatan() =
member private this.Normalkan(judul: string) =
CatatanBantu.CariKataKunci judul
member this.Simpan(judul: string) =
printfn "Disimpan: %s" (this.Normalkan judul)
member public this.Jumlah = 1
let arsip = ArsipCatatan()
arsip.Simpan(" Laporan Mingguan ")
printfn "Jumlah: %d" arsip.JumlahDisimpan: laporan mingguan
Jumlah: 1Fungsi rapikan hanya dapat dipanggil dari dalam CatatanBantu, sedangkan CariKataKunci dapat dipanggil dari tipe mana pun dalam assembly yang sama, termasuk oleh ArsipCatatan. Member Normalkan pada kelas juga privat, tetapi pemanggilannya berasal dari dalam kelas itu sendiri sehingga sah. Member Simpan menjadi pintu masuk publik yang bermakna, sedangkan Jumlah ditulis member public secara eksplisit hanya sebagai penanda bahwa bentuk itu memang sah.
Ketika compiler menolak akses#
Percobaan memanggil member privat dari luar kelasnya ditolak pada waktu kompilasi, bukan pada waktu program berjalan. Skrip berikut sengaja memuat pemanggilan semacam itu.
// tidak-dijalankan: skrip ini dieksekusi lewat perintah terminal pada blok berikutnya
// skrip ini sengaja mengakses member privat dari luar kelasnya
type ArsipCatatan() =
member private this.Normalkan(judul: string) =
judul.Trim().ToLower()
let arsip = ArsipCatatan()
printfn "%s" (arsip.Normalkan("Laporan"))# menjalankan skrip yang sengaja melanggar batas akses
# jalur berkas pada pesan error dipangkas agar ringkas
dotnet fsi akses-salah.fsx 2>&1 | sed "s|.*/||"akses-salah.fsx(8,15): error FS0491: The member or object constructor 'Normalkan' is not accessible. Private members may only be accessed from within the declaring type. Protected members may only be accessed from an extending type and cannot be accessed from inner lambda expressions.Pesan FS0491 menyatakan member Normalkan tidak dapat diakses dan menjelaskan batasnya, yaitu member privat hanya boleh diakses dari tipe yang menyatakannya. Nomor baris pada awal pesan menunjuk pemanggilan yang bermasalah. Perbaikannya ada dua arah, yaitu membuka member menjadi publik atau internal bila akses lintas batas memang diperlukan, atau menyediakan member publik lain yang bermakna seperti Simpan pada contoh sebelumnya.
Bentuk penolakan lain muncul ketika binding let di dalam kelas dibaca dari luar, sebagaimana skrip berikut.
// tidak-dijalankan: skrip ini dieksekusi lewat perintah terminal pada blok berikutnya
// skrip ini sengaja membaca binding let kelas dari luar
type ArsipCatatan() =
let jumlah = 3
member this.Buka() = jumlah
let arsip = ArsipCatatan()
printfn "%d" arsip.jumlah# menjalankan skrip yang membaca field let dari luar kelasnya
# jalur berkas pada pesan error dipangkas agar ringkas
dotnet fsi field-let-salah.fsx 2>&1 | sed "s|.*/||"field-let-salah.fsx(8,20): error FS0039: The type 'ArsipCatatan' does not define the field, constructor or member 'jumlah'.Bandingkan kedua pesan tersebut. Pesan FS0491 mengakui keberadaan member tetapi menolak aksesnya, sedangkan pesan FS0039 menyatakan member itu tidak didefinisikan sama sekali. Binding let di dalam kelas bukan member dan tidak pernah terlihat dari luar, sehingga satu-satunya jalan menuju nilainya adalah member yang mengeksposnya secara sengaja, seperti Buka pada skrip di atas.
Pewarisan dan override#
Pewarisan dinyatakan dengan inherit pada baris pertama badan kelas turunan, diikuti konstruktor kelas dasarnya. Anggota yang dimaksudkan untuk ditimpa (override) dinyatakan pada kelas dasar sebagai abstract member beserta tanda tangannya, lalu kelas turunan memberikan badannya dengan override. Kelas dasar yang memuat abstract member tanpa badan wajib diberi atribut [<AbstractClass>]. Kelas semacam itu tidak dapat dibangun menjadi objek, tetapi tetap dapat dijadikan tipe variabel maupun parameter.
// Kelas abstrak dengan abstract member dan member konkret
[<AbstractClass>]
type PengolahNilai() =
abstract AmbangLulus : int
abstract Klasifikasi : int -> string
// member konkret boleh memanggil abstract member
member this.Lulus(nilai: int) =
nilai >= this.AmbangLulus
// Turunan pertama
type KelasReguler() =
inherit PengolahNilai()
override this.AmbangLulus = 75
override this.Klasifikasi(nilai) =
if nilai >= this.AmbangLulus then "Lulus" else "Tidak lulus"
// Turunan kedua dengan ambang berbeda
type KelasPerbaikan() =
inherit PengolahNilai()
override this.AmbangLulus = 55
override this.Klasifikasi(nilai) =
if nilai >= this.AmbangLulus then "Lulus" else "Tidak lulus"
let pengolah: PengolahNilai list = [ KelasReguler(); KelasPerbaikan() ]
for p in pengolah do
printfn "%s" (p.Klasifikasi(70))
printfn "Lulus 70: %b" (p.Lulus(70))Tidak lulus
Lulus 70: false
Lulus
Lulus 70: trueDua kelas turunan memberikan arti berbeda untuk AmbangLulus dan Klasifikasi, sedangkan member konkret Lulus pada kelas dasar memanggil AmbangLulus tanpa mengetahui kelas turunannya. Nilai AmbangLulus dievaluasi sesuai kelas objek pada waktu berjalan, sehingga nilai 70 menghasilkan keputusan berbeda untuk kedua kelas. Daftar bertipe PengolahNilai list menampung kedua turunan sekaligus, dan pemanggilan melalui kelas dasar menjalankan badan milik masing-masing objek.
Kelas abstrak tidak dapat dibangun#
Atribut [<AbstractClass>] menandai kelas yang sengaja tidak lengkap, sehingga perannya hanya sebagai cetak biru bagi turunannya. Percobaan membangun objek langsung dari kelas semacam itu ditolak compiler dengan pesan berikut.
// tidak-dijalankan: skrip ini dieksekusi lewat perintah terminal pada blok berikutnya
// skrip ini sengaja membangun objek dari kelas abstrak
[<AbstractClass>]
type PengolahNilai() =
abstract AmbangLulus : int
let pengolah = PengolahNilai()# menjalankan skrip yang membangun objek dari kelas abstrak
# jalur berkas pada pesan error dipangkas agar ringkas
dotnet fsi abstrak-dibangun.fsx 2>&1 | sed "s|.*/||"abstrak-dibangun.fsx(7,16): error FS0759: Instances of this type cannot be created since it has been marked abstract or not all methods have been given implementations. Consider using an object expression '{ new ... with ... }' instead.Pesan FS0759 menawarkan dua jalan keluar, yaitu menurunkan kelasnya lalu mengimplementasikan anggota abstraknya, atau menggunakan object expression berbentuk { new PengolahNilai with ... } apabila implementasi sekali pakai sudah cukup. Pemisahan ini menjaga kontrak tetap jelas, karena kelas abstrak hanya berupa cetak biru, sedangkan objek selalu lahir dari kelas konkret yang anggota abstraknya telah lengkap.
Ringkasan#
publicmerupakan akses bawaan,privatemembatasi ke tipe atau modul yang menyatakannya, daninternalmembatasi ke satu assembly.- Akses member privat dari luar memicu error
FS0491, sedangkan bindingletkelas tidak terlihat sama sekali dari luar dan memicu errorFS0039. inheritmewariskan kelas dasar,abstract membermenandai anggota yang wajib ditimpa, dan[<AbstractClass>]menandai kelas yang tidak dapat dibangun menjadi objek dengan errorFS0759bila dilanggar.
Referensi#
- Access Control — Microsoft Learn — Modifier public, private, dan internal pada tipe, member, serta modul.
- Inheritance — Microsoft Learn — Aturan inherit, override, dan pemanggilan konstruktor kelas dasar.
- Abstract Classes — Microsoft Learn — Atribut AbstractClass dan penandaan member abstrak pada kelas dasar.