Aksesibilitas dan pewarisan

Modifier akses, overriding anggota, dan kelas abstrak di dalam F#.

Daftar isi

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.

ModifierBatas akses
publicsemua kode, berlaku secara bawaan
privatetipe atau modul yang menyatakannya
internalseluruh 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.

fsharp akses-tiga-tingkat.fsx
// 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.Jumlah
text Keluaran
Disimpan: laporan mingguan
Jumlah: 1

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

fsharp akses-salah.fsx
// 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"))
bash
# menjalankan skrip yang sengaja melanggar batas akses
# jalur berkas pada pesan error dipangkas agar ringkas
dotnet fsi akses-salah.fsx 2>&1 | sed "s|.*/||"
text Keluaran
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.

fsharp field-let-salah.fsx
// 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
bash
# 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|.*/||"
text Keluaran
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.

fsharp pewarisan-nilai.fsx
// 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))
text Keluaran
Tidak lulus
Lulus 70: false
Lulus
Lulus 70: true

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

fsharp abstrak-dibangun.fsx
// 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()
bash
# 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|.*/||"
text Keluaran
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#

  • public merupakan akses bawaan, private membatasi ke tipe atau modul yang menyatakannya, dan internal membatasi ke satu assembly.
  • Akses member privat dari luar memicu error FS0491, sedangkan binding let kelas tidak terlihat sama sekali dari luar dan memicu error FS0039.
  • inherit mewariskan kelas dasar, abstract member menandai anggota yang wajib ditimpa, dan [<AbstractClass>] menandai kelas yang tidak dapat dibangun menjadi objek dengan error FS0759 bila dilanggar.

Referensi#