Antarmuka

Kontrak Interface dengan Implements dan polimorfisme melalui antarmuka.

Daftar isi

Antarmuka (interface) adalah deklarasi kumpulan anggota tanpa badan yang berperan sebagai kontrak, sedangkan kelas yang mengikat dirinya pada kontrak itu wajib menyediakan seluruh implementasinya. Topik ini membahas pernyataan Interface, pengikatan dengan klausa Implements, serta polimorfisme yang muncul ketika objek dari kelas berbeda diproses melalui satu variabel bertipe antarmuka. Contoh yang dijalankan mendefinisikan antarmuka IPenyimpanData dengan dua kelas penyimpan yang berbeda cara kerjanya, lalu memproses keduanya dalam satu perulangan yang sama.

Kontrak tanpa implementasi#

Pernyataan Interface NamaAntarmuka mendeklarasikan daftar anggota yang hanya berupa kepala prosedur, yaitu Sub, Function, Property, dan Event tanpa isi apa pun. Antarmuka tidak menyimpan keadaan, tidak memiliki field, dan tidak pernah menjalankan kode sendiri, sehingga semuanya diserahkan kepada kelas yang mengimplementasikannya. Konvensi penamaan yang lazim memberi awalan I pada antarmuka, seperti IPenyimpanData, untuk membedakannya dari nama kelas pada pembacaan kode.

Kelas menyatakan keikutsertaannya dengan klausa Implements NamaAntarmuka setelah baris Class, dan setiap anggota antarmuka wajib dipasangkan dengan satu anggota kelas melalui sufiks Implements NamaAntarmuka.NamaAnggota. Keterikatan ini diperiksa compiler, sehingga kelas yang kehilangan satu anggota kontrak langsung ditolak pada waktu kompilasi, tanpa menunggu error muncul ketika program berjalan. Kontrak semacam ini berguna ketika beberapa kelas dengan cara kerja yang benar-benar berbeda harus melayani pemanggil yang sama.

Mengimplementasikan antarmuka pada dua kelas#

Program berikut memuat antarmuka IPenyimpanData beserta dua kelas implementasinya. PenyimpanBerkas menghitung total karakter yang diterimanya, sedangkan PenyimpanMemori menghitung jumlah catatan yang diterimanya. Kedua kelas mengikuti kontrak yang sama, tetapi cara menyimpannya sama sekali berbeda, dan perbedaan itu tidak terlihat oleh kode yang memanggilnya.

vb Program.vb
Interface IPenyimpanData

    Sub Simpan(judul As String, isi As String)
    Function Ringkasan() As String

End Interface

Class PenyimpanBerkas
    Implements IPenyimpanData

    Private jumlahKarakter As Integer

    Public Sub Simpan(judul As String, isi As String) Implements IPenyimpanData.Simpan
        jumlahKarakter += judul.Length + isi.Length
    End Sub

    Public Function Ringkasan() As String Implements IPenyimpanData.Ringkasan
        Return $"berkas teks: {jumlahKarakter} karakter tercatat"
    End Function

End Class

Class PenyimpanMemori
    Implements IPenyimpanData

    Private jumlahCatatan As Integer

    Public Sub Simpan(judul As String, isi As String) Implements IPenyimpanData.Simpan
        jumlahCatatan += 1
    End Sub

    Public Function Ringkasan() As String Implements IPenyimpanData.Ringkasan
        Return $"memori proses: {jumlahCatatan} catatan tercatat"
    End Function

End Class

Module Program

    Sub Main()
        Dim berkas As New PenyimpanBerkas()
        Dim memori As New PenyimpanMemori()

        ' dua tipe berbeda dipanggil melalui satu tipe antarmuka yang sama
        Dim target() As IPenyimpanData = {berkas, memori}

        For Each penyimpan As IPenyimpanData In target
            penyimpan.Simpan("Laporan Piket", "Piket sore: Dewi")
            Console.WriteLine(penyimpan.Ringkasan())
        Next
    End Sub

End Module
text Keluaran
berkas teks: 29 karakter tercatat
memori proses: 1 catatan tercatat

Keluaran menunjukkan dua laporan berbeda dari satu perulangan yang sama. Pada iterasi pertama, variabel penyimpan menampung objek PenyimpanBerkas, sehingga Simpan menambah jumlah karakter dan Ringkasan melaporkan 29 karakter. Jumlah itu berasal dari judul 13 karakter ditambah isi 16 karakter. Pada iterasi kedua, variabel yang sama menampung PenyimpanMemori, sehingga Simpan menambah satu pada hitungan catatan. Nama prosedur yang dipanggil identik, tetapi badan yang dijalankan mengikuti kelas objeknya.

vb Contoh: anggota tanpa klausa Implements ditolak compiler
' tidak-dijalankan
Class PenyimpanBerkas
    Implements IPenyimpanData

    Public Sub Simpan(judul As String, isi As String)
        ' error BC30149: kelas wajib mengimplementasikan anggota antarmukanya
    End Sub

End Class

Fragment di atas memperlihatkan kegagalan yang paling sering terjadi, yaitu kepala prosedur sudah ditulis dengan benar tetapi sufiks Implements IPenyimpanData.Simpan tertinggal. Tanpa sufiks itu compiler tidak menganggap keduanya berpasangan, dan pesan error BC30149 menuntut kelas mengimplementasikan anggota antarmukanya. Perbaikannya cukup menambahkan sufiks pada baris deklarasinya.

Polimorfisme melalui tipe antarmuka#

Variabel yang bertipe antarmuka dapat menampung objek dari kelas mana pun yang mengimplementasikannya. Pada program, array target() As IPenyimpanData menampung PenyimpanBerkas dan PenyimpanMemori sekaligus, dan perulangan For Each memanggil Simpan serta Ringkasan tanpa mengetahui kelas aslinya. Inilah polimorfisme (polymorphism) melalui antarmuka, yaitu satu tipe kontrak yang berlaku untuk banyak wujud implementasi.

Keuntungan dari pemisahan ini terasa ketika implementasi baru ditambahkan. Kelas PenyimpanArsip yang juga mengimplementasikan IPenyimpanData dapat disisipkan ke array target tanpa mengubah satu baris perulangan, karena perulangan hanya bergantung pada kontrak dan bukan pada daftar kelasnya. Pemanggil yang hanya melihat tipe IPenyimpanData pun terlindungi dari perubahan internal, sehingga kode yang memenuhi kontrak dan kode yang menggunakannya dapat berkembang terpisah.

Antarmuka dan pewarisan kelas#

Antarmuka dan pewarisan kelas melengkapi satu sama lain dan bekerja pada sumber yang berbeda. Pewarisan kelas mewariskan implementasi siap pakai dari satu kelas induk, sedangkan antarmuka hanya menjanjikan bentuk anggota dan boleh diambil lebih dari satu. Satu kelas VB.NET boleh mewarisi satu kelas induk sekaligus mengimplementasikan beberapa antarmuka, sehingga kemampuan dari beberapa kontrak dapat dikumpulkan pada satu kelas tanpa melanggar larangan pewarisan ganda.

Pilihan antara keduanya mengikuti kebutuhan. Apabila turunan perlu mewarisi logika yang sudah jadi dari induknya, pewarisan kelas adalah pilihan yang tepat, sedangkan jika yang dibutuhkan hanyalah janji bahwa beberapa kelas menyediakan anggota yang sama, antarmuka lebih ringan dan tidak mengunci kelasnya pada satu induk. Kelas yang dirancang dengan antarmuka juga lebih mudah diuji terpisah, karena kontraknya dapat dipenuhi oleh kelas tiruan yang perilakunya disiapkan khusus untuk pengujian.

Sebuah kelas juga dapat menyatakan keduanya sekaligus, yaitu mewarisi kelas induk untuk kode bersamanya sambil mengimplementasikan antarmuka untuk janji kemampuannya. Dengan pembagian itu, keputusan desain tidak lagi berebut satu slot pewarisan, karena peran keduanya sudah terpisah sejak awal. Slot pewarisan tetap satu, sedangkan jumlah antarmuka yang dapat diimplementasikan tidak dibatasi satu.

Kesalahan umum#

Kesalahan pertama adalah melupakan sufiks Implements pada anggota kelas, sehingga compiler menuntut implementasi yang belum dipasangkan seperti pada pesan BC30149. Deklarasi Implements NamaAntarmuka pada kelas saja tidak cukup, karena pengikatan dilakukan per anggota. Perbaikannya adalah menambahkan sufiks pada setiap anggota yang mengisi kontrak.

Kesalahan kedua adalah mencoba membuat objek dari antarmuka dengan New IPenyimpanData(). Compiler menolaknya karena antarmuka tidak memiliki konstruktor dan tidak menyimpan keadaan apa pun, dan variabel bertipe antarmuka wajib diisi dengan objek dari kelas implementasinya. Bentuk yang benar adalah Dim x As IPenyimpanData = New PenyimpanBerkas() atau langsung mendeklarasikannya dengan Dim x As New PenyimpanBerkas().

Kesalahan ketiga adalah memanggil anggota kelas yang tidak termasuk dalam kontrak melalui variabel antarmuka. Variabel penyimpan As IPenyimpanData hanya mengenal Simpan dan Ringkasan, sehingga anggota lain milik PenyimpanBerkas tidak terlihat dari variabel itu meskipun objeknya memiliki anggota tersebut. Apabila anggota kelasnya memang diperlukan, variabelnya bertipe kelas yang bersangkutan, atau kemampuan itu ditambahkan ke dalam kontrak antarmukanya.

Ringkasan#

  • Interface mendeklarasikan kontrak berupa kepala anggota tanpa badan, dan kelas mengisi seluruh anggotanya melalui klausa Implements pada kelas serta sufiks Implements per anggota.
  • Variabel bertipe antarmuka menampung objek dari kelas implementasi mana pun, sehingga satu perulangan dapat memproses kelas-kelas berbeda secara polimorfis.
  • Antarmuka memisahkan kontrak dari implementasinya, boleh diambil lebih dari satu oleh kelas yang sama, dan menjadi jalan keluar larangan pewarisan ganda.

Referensi#