Pewarisan (inheritance) dan polimorfisme (polymorphism) adalah dua sarana Fortran modern untuk membangun keluarga tipe yang saling berhubungan, yaitu satu tipe mewarisi komponen dan prosedur terikat tipe dari tipe lainnya, sedangkan satu variabel dapat menampung objek dari tipe mana pun sekeluarga. Topik ini membahas deklarasi extends, tipe abstract dengan prosedur deferred, deklarasi class, pemilihan implementasi saat program berjalan melalui dispatch dinamis (dynamic dispatch), dan penelaahan tipe dinamis dengan select type. Satu program geometri disertakan dan dijalankan, sehingga setiap bentuk pemanggilan dapat diuji ulang.
Pewarisan dengan extends dan tipe abstract#
Deklarasi type, extends(bentuk) :: persegi menjadikan persegi turunan dari bentuk, sehingga seluruh komponen dan prosedur terikat milik bentuk otomatis dimiliki persegi tanpa ditulis ulang. Setiap turunan menambahkan komponen barunya sendiri, yaitu sisi pada persegi dan radius pada lingkaran, dan boleh menimpa (override) prosedur terikat yang diwarisinya dengan mengarahkan nama binding yang sama ke prosedur yang berbeda. Dengan demikian, satu binding luas memiliki tiga implementasi, satu untuk setiap anggota keluarga.
Tipe dasar yang tidak memiliki rumus luas sendiri dinyatakan abstract, yaitu type, abstract :: bentuk. Tipe abstract tidak dapat dipakai membuat objek, tetapi tetap dapat mendeklarasikan kontrak yang wajib dipatuhi seluruh turunannya, melalui procedure(luas_antarmuka), deferred :: luas. Kata deferred berarti implementasinya ditunda ke setiap turunan, sedangkan luas_antarmuka adalah antarmuka abstract yang mendefinisikan bentuk argumennya. Compiler menolak turunan yang lupa menyediakan implementasinya, sehingga kontrak keluarga tipe dijaga pada saat kompilasi, bukan menjadi kesepakatan yang mengandalkan disiplin penulis.
! tidak-dijalankan
type, abstract :: bentuk
character(len=16) :: nama = "bentuk"
contains
procedure(luas_antarmuka), deferred :: luas
end type bentuk
abstract interface
real function luas_antarmuka(self)
import :: bentuk
class(bentuk), intent(in) :: self
end function luas_antarmuka
end interfaceAntarmuka abstract di atas menyatakan bahwa implementasi luas menerima satu argumen bertipe class(bentuk) dan mengembalikan real, dan pernyataan import membawa nama bentuk ke dalam lingkup antarmuka tersebut. Implementasi milik persegi menghitung self%sisi ** 2, sedangkan milik lingkaran menghitung pi * self%radius ** 2, dan keduanya menandatangani kontrak yang sama.
Deklarasi class dan dispatch dinamis#
Variabel yang dapat menampung objek dari tipe mana pun pada satu keluarga dideklarasikan dengan class, misalnya class(bentuk), allocatable :: b. Variabel semacam itu memiliki dua tipe sekaligus, yaitu tipe deklarasinya yang selalu bentuk, dan tipe dinamis (dynamic type) yang menyatakan tipe objek yang sedang disimpannya. Penugasan b = persegi(nama="persegi-1", sisi=4.0) mengalokasikan memori sesuai tipe dinamis yang baru, dan penugasan berikutnya dengan tipe lain melepas alokasi lama lalu mengalokasikan ulang secara otomatis.
Kekuatan polimorfisme terletak pada pemanggilan prosedur terikat melalui variabel semacam itu. Pada satu pernyataan b%luas(), implementasinya dipilih menurut tipe dinamis pada saat program berjalan, sehingga baris yang sama persis menghasilkan 16.00 ketika b menyimpan persegi bersisi empat, dan 153.94 ketika b menyimpan lingkaran beradius tujuh. Mekanisme ini adalah dispatch dinamis, yaitu keputusan pemilihan prosedur ditunda sampai eksekusi, sementara penulisnya cukup menulis satu pemanggilan untuk seluruh keluarga tipe.
Program uji bentuk#
Program berikut menyatukan seluruh konsep yang telah dibahas, yaitu pewarisan, tipe abstract, deklarasi class, dan dispatch dinamis, dalam satu modul geometri dan satu unit program. Tipe kanvas pada bagian akhir modul menunjukkan komponen polimorfik, yaitu satu wadah class(bentuk), allocatable yang dapat menyimpan bentuk apa pun sekeluarga.
module geometri
implicit none
real, parameter :: pi = 3.14159265
type, abstract :: bentuk
character(len=16) :: nama = "bentuk"
contains
procedure(luas_antarmuka), deferred :: luas
end type bentuk
abstract interface
real function luas_antarmuka(self)
import :: bentuk
class(bentuk), intent(in) :: self
end function luas_antarmuka
end interface
type, extends(bentuk) :: persegi
real :: sisi = 0.0
contains
procedure :: luas => luas_persegi
end type persegi
type, extends(bentuk) :: lingkaran
real :: radius = 0.0
contains
procedure :: luas => luas_lingkaran
end type lingkaran
type :: kanvas
character(len=16) :: judul
class(bentuk), allocatable :: isi
end type kanvas
contains
real function luas_persegi(self)
class(persegi), intent(in) :: self
luas_persegi = self%sisi ** 2
end function luas_persegi
real function luas_lingkaran(self)
class(lingkaran), intent(in) :: self
luas_lingkaran = pi * self%radius ** 2
end function luas_lingkaran
end module geometri
program uji_bentuk
use geometri
implicit none
class(bentuk), allocatable :: b
class(bentuk), allocatable :: c
type(kanvas) :: k
b = persegi(nama="persegi-1", sisi=4.0)
print '(1X, A16, 1X, A, F10.2)', b%nama, "luas:", b%luas()
b = lingkaran(nama="lingkaran-1", radius=7.0)
print '(1X, A16, 1X, A, F10.2)', b%nama, "luas:", b%luas()
allocate(persegi :: c)
print '(1X, A16, 1X, A, F10.2)', c%nama, "luas:", c%luas()
deallocate(c)
k = kanvas("kanvas-utama", persegi(sisi=2.0, nama="persegi-4"))
print '(1X, A, A, F10.2)', trim(k%judul), " luas isi:", k%isi%luas()
call periksa(persegi(sisi=3.0, nama="persegi-3"))
call periksa(lingkaran(radius=2.0, nama="lingkaran-3"))
contains
subroutine periksa(x)
class(bentuk), intent(in) :: x
select type (x)
type is (persegi)
print '(1X, A, A, A, F6.2, A, F8.2)', "persegi: ", trim(x%nama), " sisi: ", &
x%sisi, " luas: ", x%luas()
type is (lingkaran)
print '(1X, A, A, A, F6.2, A, F8.2)', "lingkaran: ", trim(x%nama), " radius: ", &
x%radius, " luas: ", x%luas()
class default
print '(1X, A)', "bentuk umum tanpa rumus khusus"
end select
end subroutine periksa
end program uji_bentuk# mengompilasi program dari direktori topik
gfortran polimorfisme.f90 -o polimorfisme
# menjalankan program hasil kompilasi
./polimorfisme persegi-1 luas: 16.00
lingkaran-1 luas: 153.94
bentuk luas: 0.00
kanvas-utama luas isi: 4.00
persegi: persegi-3 sisi: 3.00 luas: 9.00
lingkaran: lingkaran-3 radius: 2.00 luas: 12.57Dua baris pertama memperlihatkan satu pemanggilan b%luas() yang menghasilkan nilai berbeda karena tipe dinamisnya berganti, sedangkan nama bindingnya tidak berubah. Baris ketiga berasal dari allocate(persegi :: c) yang membuat objek tanpa nilai eksplisit, sehingga komponen nama memperoleh nilai bawaan warisan bentuk dan luasnya nol, dan baris ini sekaligus membuktikan bahwa tipe abstract tetap dapat menjadi tipe deklarasi meskipun tidak dapat dibuat objeknya. Baris kanvas memperlihatkan komponen polimorfik isi yang menyimpan satu persegi dan dihitung luasnya lewat dispatch yang sama.
Menelaah tipe dinamis dengan select type#
Di dalam subrutin periksa, argumen x dideklarasikan class(bentuk), intent(in), sehingga subrutin yang sama menerima persegi maupun lingkaran. Konsekuensinya, compiler hanya mengizinkan akses ke komponen yang dimiliki tipe deklarasi, dan percobaan membaca x%sisi di luar penelaahan tipe ditolak dengan pesan error yang menyatakan sisi bukan anggota bentuk. Konstruksi select type (x) membuka akses tersebut, karena di dalam cabang type is (persegi) compiler mengetahui tipe dinamisnya, sehingga komponen sisi sah diakses.
Cabang type is (persegi) cocok bila tipe dinamisnya tepat persegi, dan type is (lingkaran) cocok bila tepat lingkaran. Cabang class default menampung seluruh sisa kasus dan menjadi pengaman bila keluarga tipe berkembang dengan anggota baru. Pola argumen polimorfik yang ditelaah dengan select type adalah bentuk yang paling umum dari kode yang bekerja pada kumpulan objek yang tipenya beragam.
Kesalahan umum#
Kesalahan pertama adalah mencoba membuat objek dari tipe abstract dengan deklarasi type(bentuk) :: x, yang ditolak compiler karena tipe abstract memang tidak boleh dijadikan tipe konkret. Perbaikannya adalah mendeklarasikan variabel polimorfik class(bentuk), allocatable :: x dan mengisinya dengan turunan yang konkret. Kesalahan kedua adalah mendeklarasikan variabel polimorfik tanpa atribut, karena variabel class yang berdiri sendiri wajib diberi atribut allocatable atau pointer, atau menjadi argumen dummy prosedur.
Kesalahan ketiga adalah mengakses komponen milik turunan langsung dari variabel yang dideklarasikan dengan tipe dasarnya, misalnya membaca x%sisi dari x bertipe deklarasi bentuk. Compiler menolak akses itu karena tipe deklarasinya tidak menjamin adanya komponen sisi, dan jalurnya adalah memindahkan akses ke dalam cabang select type yang sesuai. Kesalahan keempat adalah lupa menyediakan implementasi prosedur deferred pada sebuah turunan, dan hal ini tertangkap saat kompilasi, sehingga kontrak keluarga tipe tidak pernah terlewati diam-diam.
Ringkasan#
extendsmewariskan komponen dan prosedur terikat tipe ke turunannya, sedangkan tipe abstract dengan prosedur deferred menetapkan kontrak yang wajib diimplementasikan setiap turunan.- Variabel
classmenyimpan objek dari tipe mana pun sekeluarga, dan pemanggilan prosedur terikatnya dipilih menurut tipe dinamis pada saat berjalan melalui dispatch dinamis. select typedengan cabangtype isdanclass defaultmembuka akses ke komponen khusus turunan, dan komponenclass(bentuk), allocatablemenjadi wadah heterogen di dalam tipe lain.
Referensi#
- Learn: tutorial resmi Fortran — fortran-lang — Kumpulan tutorial resmi komunitas Fortran yang menjadi rujukan fitur bahasa pada jalur ini.
- Fortran Object Oriented Programming — fortran-lang — Artikel resmi komunitas tentang penerapan fitur berorientasi objek pada Fortran modern.
- The GNU Fortran Compiler — GNU Project — Manual gfortran, compiler yang digunakan untuk menjalankan seluruh contoh kode pada jalur ini.