Extends dan polimorfisme

Pewarisan dengan extends, deklarasi class, dan dispatch dinamis melalui select type.

Daftar isi

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.

fortran Contoh: kontrak luas pada tipe abstract
! 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 interface

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

fortran polimorfisme.f90
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
bash
# mengompilasi program dari direktori topik
gfortran polimorfisme.f90 -o polimorfisme
# menjalankan program hasil kompilasi
./polimorfisme
text Keluaran
 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.57

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

  • extends mewariskan komponen dan prosedur terikat tipe ke turunannya, sedangkan tipe abstract dengan prosedur deferred menetapkan kontrak yang wajib diimplementasikan setiap turunan.
  • Variabel class menyimpan objek dari tipe mana pun sekeluarga, dan pemanggilan prosedur terikatnya dipilih menurut tipe dinamis pada saat berjalan melalui dispatch dinamis.
  • select type dengan cabang type is dan class default membuka akses ke komponen khusus turunan, dan komponen class(bentuk), allocatable menjadi wadah heterogen di dalam tipe lain.

Referensi#