Metode dan pewarisan

Pemanggilan metode melalui panah, pewarisan dengan use parent, dan urutan resolusi metode.

Daftar isi

Pemanggilan metode melalui operator panah, pewarisan antarkelas, dan urutan pencarian metode membentuk tulang punggung OOP klasik Perl. Topik ini membahas peran invocant pada setiap pemanggilan, pewarisan melalui use parent, penimpaan metode beserta pemanggilan versi induk melalui SUPER::, serta garis besar urutan pencarian metode. Satu skrip yang berisi kelas induk Bentuk, kelas anak PersegiPanjang, dan kelas cucu Persegi ditulis dan dijalankan untuk menunjukkan seluruh mekanismenya.

Operator panah dan invocant#

Setiap pemanggilan metode di Perl mengikuti satu aturan yang sama, yaitu operator panah menyisipkan invocant sebagai argumen pertama subrutin. Pada $persegi->luas, Perl mencari subrutin luas pada kelas objek $persegi, lalu memanggilnya dengan objek tersebut sebagai argumen pertama. Bentuk pemanggilannya setara dengan subrutin biasa yang diberi argumen secara eksplisit.

perl Contoh: pemanggilan metode dan padanan subrutinnya
# tidak-dijalankan
my $luas1 = $persegi->luas;                  # metode, invocant disisipkan otomatis
my $luas2 = PersegiPanjang::luas($persegi);  # subrutin, invocant disisipkan manual

Kedua baris tersebut menghasilkan nilai yang sama pada kelas yang tidak mewarisi apa pun, tetapi keduanya tidak setara pada kelas yang mewarisi metode. Bentuk Kelas::metode(...) melompat langsung ke subrutin pada kelas yang disebut, sehingga pencarian metode beserta pewarisannya dilewati. Karena itu, panggil metode selalu melalui operator panah agar mekanisme pencariannya tetap bekerja. Operator panah juga melayani pemanggilan kelas seperti Persegi->new, dengan nama kelas berperan sebagai invocant.

Nilai invocant menentukan bentuk argumen pertamanya, yaitu string nama kelas pada pemanggilan kelas dan referensi yang sudah ditandai pada pemanggilan objek. Konvensi penamaan mengikuti peran itu, sehingga konstruktor menampungnya pada variabel $class dan metode objek menampungnya pada $self. Operator panah juga dapat dirangkai, karena metode yang mengembalikan objek dapat langsung diikuti pemanggilan metode berikutnya melalui panah kedua.

Pewarisan dengan use parent#

Pewarisan menghubungkan kelas anak dengan kelas induknya, sehingga metode yang tidak dimiliki kelas anak dicari pada kelas induk. Perl menyimpan hubungan ini pada array package @ISA, dan pragma use parent mengisinya sekaligus menghubungkan berkas kelas induknya. Pernyataan use parent 'Bentuk'; pada kelas PersegiPanjang menjadikan Bentuk induknya, dan sejak saat itu seluruh metode Bentuk tersedia pada PersegiPanjang tanpa ditulis ulang.

Pragma tersebut menambahkan Bentuk ke @PersegiPanjang::ISA dan memuat berkas Bentuk.pm apabila berkas itu belum dimuat. Apabila kelas induk didefinisikan pada berkas yang sama dengan kelas anaknya, bentuk -norequire digunakan agar Perl tidak mencari berkas yang memang tidak ada. Bentuk -norequire inilah yang digunakan pada skrip dalam topik ini, karena ketiga kelasnya berada dalam satu berkas.

perl Contoh: dua bentuk penulisan use parent
# tidak-dijalankan
# kelas induk dimuat dari berkasnya sendiri, misalnya Bentuk.pm
use parent 'Bentuk';

# kelas induk sudah didefinisikan pada berkas yang sama
use parent -norequire, 'Bentuk';

Setelah @ISA terisi, metode induk otomatis menjadi milik kelas anak. Pemanggilan $persegi->nama pada objek kelas Persegi menjalankan subrutin nama yang didefinisikan pada Bentuk, tanpa satu baris kode penyalinan pun pada kelas Persegi. Tidak ada salinan subrutin yang dibuat, sehingga perbaikan pada metode induk otomatis diterima seluruh kelas turunannya. Hubungan pada @ISA inilah yang menentukan arah pencarian metode pada bagian berikutnya.

Menimpa metode dan memanggil versi induk#

Kelas anak dapat mendefinisikan ulang metode induknya, dan perilaku ini disebut penimpaan (override). Metode pada kelas anak mengalahkan metode induk yang sejenis, karena pencarian metode berhenti pada kelas pertama yang memilikinya. Penimpaan digunakan saat perilaku bawaan induk belum sesuai dengan kebutuhan kelas anak, misalnya cara menghitung ukuran atau cara menampilkan deskripsi.

Penimpaan tidak selalu menggantikan seluruh isi metode induknya. Notasi SUPER:: memanggil versi induk dari dalam metode anak, sehingga anak dapat memperluas perilakunya. Pada konstruktor, pola $class->SUPER::new(%argumen) membentuk objek melalui kelas induk, lalu kelas anak menambahkan atribut tambahannya pada hash yang sama. Pola ini menjaga satu titik pembentukan objek dan menghindari duplikasi baris bless.

Metode deskripsi pada kelas PersegiPanjang memperlihatkan pola yang sama pada aksesor. Versi anaknya memanggil $self->SUPER::deskripsi() untuk memperoleh teks dasar dari Bentuk, lalu menambahkan keterangan luasnya. Dengan cara ini, perubahan teks pada versi induk otomatis berlaku pada seluruh kelas turunannya. Tanpa pemanggilan tersebut, teks dasar dari Bentuk tidak akan ikut tercetak pada objek kelas anak.

Urutan pencarian metode#

Apabila sebuah metode tidak ditemukan pada kelas objeknya, Perl menelusuri rantai @ISA secara kedalaman-dulu (depth-first) dari kiri ke kanan. Pada rantai tunggal, urutannya sederhana, yaitu kelas objek terlebih dahulu, kemudian induknya, kemudian induk dari induknya, dan seterusnya hingga rantai habis. Diagram berikut menggambarkan rantai pewarisan yang digunakan pada topik ini.

PersegiPersegiPanjangBentuk@ISA@ISAPencarian metode bergerak dari kelas anak menuju kelas induk
Rantai pewarisan dari Persegi menuju Bentuk

Pemanggilan luas pada objek kelas Persegi menelusuri Persegi terlebih dahulu, kemudian PersegiPanjang, dan menemukan subrutinnya pada PersegiPanjang. Metode nama baru ditemukan pada Bentuk. Apabila seluruh rantai habis tanpa hasil, Perl menghasilkan error Can't locate object method saat program berjalan.

Perl modern menyediakan urutan resolusi lain melalui modul mro, tetapi garis besar kedalaman-dulu sudah cukup untuk membaca sebagian besar kode klasik. Pewarisan majemuk menambah percabangan pada urutan ini, sehingga rantai tunggal menjadi titik mulai yang paling mudah diprediksi. Selama rantai pewarisannya tunggal, urutan ini mudah diikuti dan kecil kemungkinannya menimbulkan kebingungan.

Kelas induk dan anak dalam satu berkas#

Skrip berikut menyatukan ketiga kelas beserta penggunaannya dalam satu berkas yang dapat dijalankan. Kelas Persegi tidak mendefinisikan satu subrutin pun, sehingga seluruh perilakunya berasal dari pewarisan. Simpan kode tersebut sebagai bentuk.pl, lalu jalankan dengan perintah perl bentuk.pl.

perl bentuk.pl
use strict;
use warnings;

# kelas induk
package Bentuk;

sub new {
    my ($class, %argumen) = @_;
    my $self = {
        nama => $argumen{nama} // "bentuk",
    };
    return bless $self, $class;
}

# metode yang akan diwarisi kelas anak
sub nama {
    my ($self) = @_;
    return $self->{nama};
}

sub deskripsi {
    my ($self) = @_;
    return "Ini $self->{nama}.";
}

# kelas anak, menimpa new dan deskripsi
package PersegiPanjang;
use parent -norequire, 'Bentuk';

sub new {
    my ($class, %argumen) = @_;
    $argumen{nama} //= "persegi panjang";
    my $self = $class->SUPER::new(%argumen);
    $self->{panjang} = $argumen{panjang} // 0;
    $self->{lebar}   = $argumen{lebar} // 0;
    return $self;
}

sub luas {
    my ($self) = @_;
    return $self->{panjang} * $self->{lebar};
}

sub deskripsi {
    my ($self) = @_;
    return $self->SUPER::deskripsi() . " Luasnya " . $self->luas . ".";
}

# kelas cucu, tidak menimpa apa pun
package Persegi;
use parent -norequire, 'PersegiPanjang';

package main;

my $bentuk = Bentuk->new(nama => "bentuk umum");
print $bentuk->deskripsi, "\n";

my $persegi = Persegi->new(panjang => 5, lebar => 5, nama => "persegi");
print "Kelas : ", ref($persegi), "\n";
print "Nama  : ", $persegi->nama, "\n";
print $persegi->deskripsi, "\n";
print "ISA   : @Persegi::ISA\n";
text Keluaran
Ini bentuk umum.
Kelas : Persegi
Nama  : persegi
Ini persegi. Luasnya 25.
ISA   : PersegiPanjang

Keluaran memperlihatkan tiga perilaku sekaligus. Objek $bentuk menjawab deskripsi dengan versi Bentuk. Objek $persegi berkelas Persegi, tetapi metode new, deskripsi, dan nama tidak didefinisikan pada kelasnya, sehingga seluruhnya diwarisi dari PersegiPanjang dan Bentuk. Metode deskripsi PersegiPanjang menimpa versi induknya, sedangkan pemanggilan SUPER::deskripsi() di dalamnya menunjukkan bahwa versi induk tetap dapat dipanggil. Baris ISA mencantumkan isi @Persegi::ISA sebagai bukti hubungan pewarisannya.

Satu berkas dapat menaungi tiga kelas sekaligus karena deklarasi package berlaku hingga deklarasi package berikutnya atau akhir berkas. Definisi Bentuk ditempatkan lebih dahulu agar package induk sudah tersedia saat use parent -norequire dijalankan pada fase kompilasi. Subrutin dengan nama yang sama pada package berbeda tidak saling menimpa karena setiap package memiliki tabel simbol sendiri, sehingga new pada Bentuk dan new pada PersegiPanjang tetap entitas yang terpisah. Deklarasi package main; menutup rangkaian definisi tersebut dan mengembalikan ruang nama utama untuk kode penggunanya.

Kesalahan umum#

Kesalahan pertama adalah menganggap pewarisan mewariskan data. Yang diwariskan adalah metode, sehingga konstruktor anak yang membentuk hash dari nol membuat atribut induk tidak pernah terisi. Perbaikannya berupa pemanggilan $class->SUPER::new(...) di dalam konstruktor anak, sehingga hash induk terbentuk terlebih dahulu sebelum atribut tambahan ditugaskan.

Kesalahan kedua adalah membentuk objek dengan bless $self, 'PersegiPanjang'; yang menuliskan nama kelas langsung pada kodenya. Konstruktor yang diwarisi kelas cucu akan tetap menghasilkan objek kelas anak, dan kelas cucu kehilangan identitasnya. Gunakan bless $self, $class; dengan nama kelas dari invocant agar konstruktor benar untuk seluruh turunannya.

Kesalahan ketiga adalah mengasumsikan urutan pencarian mengikuti urutan penulisan package pada berkas. Urutan pencarian ditentukan sepenuhnya oleh isi @ISA, dan posisi teks kelas pada berkas tidak berpengaruh apa pun. Periksa @ISA setiap kali perilaku pencarian metode terasa sulit ditebak.

Ringkasan#

  • Operator panah menyisipkan invocant sebagai argumen pertama, sedangkan bentuk Kelas::metode(...) melewati pencarian metode beserta pewarisannya.
  • use parent mengisi @ISA dan memuat berkas induk, sedangkan bentuk -norequire digunakan untuk induk yang berada dalam satu berkas.
  • Kelas anak dapat menimpa metode induk, dan SUPER:: memanggil versi induknya dari dalam metode anak.
  • Pencarian metode menelusuri rantai @ISA secara kedalaman-dulu, dari kelas objek menuju induknya, hingga metode ditemukan.

Referensi#