Kelas Abstrak

Anggota abstrak dan pola template method dengan kelas abstrak.

Daftar isi

Topik ini membahas kelas abstrak, yaitu kelas yang tidak dapat diinstansiasi secara langsung dan boleh memuat anggota yang belum diimplementasikan. Pembahasan mencakup penulisan anggota abstrak, pemanfaatannya dalam pola template method, serta pemeriksaan yang menolak instansiasi langsung. Setelah topik ini dipelajari, kerangka kelas yang memisahkan alur tetap dari variasi perhitungan dapat disusun secara terstruktur.

Anggota abstrak#

Kelas abstrak dideklarasikan dengan kata kunci abstract dan berperan sebagai kerangka bagi kelas-kelas turunannya. Objeknya hanya dapat dibuat melalui kelas turunan, karena kata kunci new tidak boleh ditujukan langsung kepada kelas abstrak. Di dalamnya terdapat anggota abstrak, yaitu properti atau metode yang hanya dideklarasikan nama dan tipenya, tanpa isi. Kelas abstrak tetap boleh memuat field, konstruktor, dan metode konkret yang diwariskan utuh kepada turunannya.

Fragmen berikut menunjukkan kedua jenis anggota itu berdampingan.

ts Kelas abstrak dengan anggota konkret
abstract class EksporLaporan {
  abstract isi(): string;

  judul(): string {
    return `Laporan: ${this.isi()}`;
  }
}

Setiap kelas turunan yang konkret wajib mengisi seluruh anggota abstrak dengan tanda tangan (signature) yang cocok. Apabila ada yang tertinggal, pemeriksa tipe menolak kelas turunan tersebut dengan error TS2515. Aturan ini menjamin tidak ada kelas konkret yang menyisakan metode tanpa isi, sehingga pemanggil selalu menerima perilaku yang lengkap.

Anggota abstrak tidak terbatas pada metode. Properti pun dapat dinyatakan abstrak, misalnya abstract namaBerkas: string, sehingga setiap turunan wajib menyediakan properti tersebut dengan tipenya. Beberapa anggota abstrak boleh dideklarasikan sekaligus pada satu kelas, dan setiap turunan konkret harus melengkapi semuanya.

Kelas abstrak dalam praktik#

Berkas analisis-nilai.ts berikut memuat kelas abstrak AnalisisNilai untuk statistik nilai ujian. Kelas ini menyimpan daftar nilai pada field protected readonly melalui konstruktornya, lalu menuntut setiap turunan mengisi metode hitung() dan label(). Metode ringkas() dimiliki kelas induk dan memakai kedua anggota abstrak tersebut.

ts analisis-nilai.ts
abstract class AnalisisNilai {
  protected readonly daftarNilai: number[];

  constructor(daftarNilai: number[]) {
    this.daftarNilai = daftarNilai;
  }

  abstract hitung(): number;
  abstract label(): string;

  ringkas(): string {
    return `${this.label()}: ${this.hitung().toFixed(2)}`;
  }
}

class RataRata extends AnalisisNilai {
  hitung(): number {
    const total = this.daftarNilai.reduce((a, b) => a + b, 0);
    return total / this.daftarNilai.length;
  }

  label(): string {
    return "rata-rata";
  }
}

class NilaiTertinggi extends AnalisisNilai {
  hitung(): number {
    return Math.max(...this.daftarNilai);
  }

  label(): string {
    return "nilai tertinggi";
  }
}

const nilaiUjian = [80, 92, 75, 88];
console.log(new RataRata(nilaiUjian).ringkas());
console.log(new NilaiTertinggi(nilaiUjian).ringkas());
text Keluaran
rata-rata: 83.75
nilai tertinggi: 92.00

Metode ringkas() dipakai bersama oleh RataRata maupun NilaiTertinggi, sedangkan kedua kelas turunan hanya menuliskan perhitungannya masing-masing. Turunan RataRata memakai reduce() untuk menjumlahkan seluruh nilai sebelum dibagi dengan banyaknya data, sedangkan NilaiTertinggi memanggil Math.max() dengan melewatkan seluruh nilai melalui tanda .... Keduanya tidak mengatur format keluaran sama sekali, karena pekerjaan itu sepenuhnya milik ringkas(). Keluaran menunjukkan format ringkasan yang seragam dari dua perhitungan yang berbeda.

Field protected readonly daftarNilai menunjukkan gabungan dua modifier yang lazim pada kelas induk. Modifier protected menutup akses dari luar hierarki, sedangkan readonly mencegah penulisan ulang setelah konstruktor selesai. Kombinasi ini membuat data yang dipakai banyak turunan menjadi stabil dan tidak mudah berubah tanpa disengaja.

Pola template method#

Template method adalah pola yang menempatkan alur tetap pada metode konkret kelas induk dan menyerahkan langkah yang bervariasi kepada anggota abstrak kelas turunan. Pada contoh sebelumnya, metode ringkas() berperan sebagai metode templat karena menyusun teks dengan format yang sama untuk semua analisis, sedangkan hitung() dan label() adalah langkah yang boleh berbeda pada setiap turunan. Pola ini menjaga konsistensi keluaran karena perubahan format cukup dilakukan pada satu tempat, yaitu kelas induknya.

Manfaat yang sama terasa ketika alur berubah. Menambahkan langkah baru pada ringkas(), misalnya penulisan tanggal analisis, cukup dilakukan satu kali dan langsung berlaku untuk seluruh turunan. Bila alur itu ditulis ulang di setiap kelas turunan, satu perubahan harus disunting di banyak tempat.

Perbandingan dengan interface membantu menentukan pilihan. Interface hanya menetapkan kontrak tanpa membawa implementasi, sedangkan kelas abstrak berbagi implementasi sekaligus menuntut pengisian bagian yang bervariasi. Apabila seluruh anggota hanya berupa deklarasi dan tidak ada alur bersama, interface sudah memadai dan struktur pewarisannya tidak terkunci. Kelas abstrak dipilih ketika ada perilaku yang ingin dipusatkan, tetapi sebagian langkah harus diserahkan kepada turunan.

Instansiasi langsung yang gagal#

Berkas instansiasi-salah.ts berikut sengaja memanggil kata kunci new terhadap kelas abstrak, sehingga berkas ini tidak dijalankan dan hanya diperiksa tipenya.

ts instansiasi-salah.ts
// tidak-dijalankan
abstract class Laporan {
  abstract isi(): string;
}

const laporan = new Laporan();
bash
# periksa tipe tanpa menghasilkan berkas keluaran
tsc --noEmit --pretty false instansiasi-salah.ts
text Keluaran
instansiasi-salah.ts(6,17): error TS2511: Cannot create an instance of an abstract class.

Error TS2511 menyatakan instance kelas abstrak tidak dapat dibuat. Penolakan ini terjadi pada saat kompilasi. Pada program hasil kompilasi, kelas abstrak menjadi kelas biasa karena kata abstract hilang bersama type erasure, yaitu penghapusan anotasi tipe dari program. Karena itu perlindungan utamanya melekat pada proses pemeriksaan tipe, sedangkan kebutuhan objek nyata selalu dipenuhi melalui kelas turunannya.

Kata kunci abstract juga sah dipasang pada kelas yang seluruh anggotanya konkret. Kelas semacam itu tetap tidak dapat diinstansiasi langsung, sehingga berguna untuk kelas induk yang sengaja dirancang hanya menjadi kerangka. Dengan cara ini niat perancangnya tertulis dalam kode, bukan hanya dalam komentar.

Kesalahan umum#

Tiga kesalahan berikut muncul ketika kelas abstrak mulai dipakai di hierarki yang lebih besar. Penyebabnya umumnya berupa asumsi tentang kewajiban turunan dan letak implementasi yang tepat. Meninjau ketiganya membantu menyeimbangkan bagian yang dibagikan dan bagian yang diserahkan.

  • Kelas turunan tidak mengisi seluruh anggota abstrak. Pemeriksa tipe menolaknya dengan error TS2515 yang menyebut anggota yang belum diimplementasikan. Perbaikannya adalah mengisi anggota tersebut atau menjadikan kelas turunannya abstrak pula.
  • Mengira kelas abstrak tidak boleh memiliki field dan konstruktor. Keduanya sah dan justru menjadi tempat data bersama, seperti daftarNilai pada contoh di atas. Anggota abstrak hanya dituntut untuk bagian yang harus berbeda pada tiap turunan.
  • Menjadikan kelas abstrak padahal tidak ada implementasi bersama. Bila seluruh anggota hanya deklarasi tanpa alur yang dipusatkan, interface lebih ringan dan tidak mengunci struktur pewarisan.

Ringkasan#

  • Kelas abstrak dideklarasikan dengan abstract, tidak dapat diinstansiasi langsung, dan selalu dipakai melalui kelas turunannya.
  • Anggota abstrak hanya berupa deklarasi, dan setiap kelas turunan konkret wajib mengisinya dengan tanda tangan yang cocok.
  • Pola template method menaruh alur tetap pada metode konkret kelas induk dan menyerahkan variasi kepada anggota abstrak.
  • Penolakan instansiasi terjadi pada saat kompilasi dengan error TS2511 karena kata abstract hilang bersama type erasure.

Referensi#