Soal 1Kamu menambahkan PdfFile ke daftar yang punya loop pemanggil file.preview(). Apa yang perlu kamu tambahkan?
Polymorphism — Pemanggilan Sama, Perilaku Berbeda
Polymorphism memberi tiap class method bernama sama agar satu bentuk pemanggilan cukup untuk semuanya. Membahas array campuran, instanceof, dan duck typing.
Bayangkan formulir kontak yang menampilkan daftar lampiran: thumbnail untuk gambar dan tombol play untuk video. Kalau jenisnya disimpan sebagai string lalu dicabangkan dengan if di function tampilan, kamu harus membuka function itu dan menambahkan kode setiap kali ada jenis baru.
Artikel ini membahas polymorphism, yaitu method bernama sama yang menjalankan kode berbeda di tiap class, dan duck typing, yang sama sekali tidak memedulikan hubungan inheritance.
Menaruh Perbedaan Tampilan di Class — Method dengan Nama yang Sama
Misalnya setiap lampiran berupa object seperti { kind: "video", fileName: "demo.mp4" }, dan function bernama previewOf membandingkan kind. Untuk menambahkan video, kamu harus membuka previewOf, yang juga berisi cabang untuk gambar, lalu menambahkan kode di sana.
Dengan polymorphism (memberi beberapa class method dengan nama yang sama, sehingga pemanggil menulis pemanggilan yang sama dan kode milik class yang membuat instance-lah yang berjalan), tampilan untuk tiap jenis ditulis di preview milik child class.
Kalau setiap child class sudah meng-override preview milik parent, pemanggil cukup menulis preview() tanpa perlu memeriksa child class mana yang sedang dipegangnya.
// Simpan jenisnya sebagai string, lalu cabangkan dengan if di function tampilan
function previewOf(file) {
if (file.kind === "image") return `${file.fileName}: Tampilkan thumbnail`;
return `${file.fileName}: Tidak ada preview`;
}
console.log(previewOf({ kind: "video", fileName: "demo.mp4" })); // demo.mp4: Tidak ada preview
// Tulis method preview dengan nama yang sama di class tiap jenis
class Attachment {
constructor(fileName) { this.fileName = fileName; }
preview() { return `${this.fileName}: Tidak ada preview`; }
}
class ImageFile extends Attachment {
preview() { return `${this.fileName}: Tampilkan thumbnail`; }
}
class VideoFile extends Attachment {
preview() { return `${this.fileName}: Tampilkan tombol play`; }
}
console.log(new ImageFile("logo.png").preview()); // logo.png: Tampilkan thumbnail
console.log(new VideoFile("demo.mp4").preview()); // demo.mp4: Tampilkan tombol play
console.log pertama menampilkan "Tidak ada preview" karena previewOf belum punya cabang untuk "video". Dengan class, siapa pun yang menambahkan video cukup memakai nama yang sama, preview, dan tidak perlu mengingat string apa saja yang masuk ke kind.
Memanggil Jenis Campuran Sekaligus — Array dan for...of
Daftar lampiran datang sebagai satu array yang mencampur gambar, PDF, dan file yang jenisnya tidak diketahui. Kalau setiap file kamu masukkan ke variabelnya sendiri lalu menulis pemanggilan preview() satu per satu, kamu harus menambah atau mengurangi baris pemanggilan setiap kali jumlah lampirannya berubah.
Instance dari class yang berbeda-beda bisa dimasukkan begitu saja ke dalam satu array. Kalau array itu kamu loop dengan for...of, file.preview() di body loop berjalan pada instance dari class yang berbeda di setiap putaran, dan pemanggilannya tetap satu baris.
Saat menambahkan PdfFile untuk PDF pun, loop-nya tidak perlu diubah.
// Attachment dan ImageFile didefinisikan sama seperti di contoh pertama
// Jenis yang ditambahkan belakangan. Loop-nya tidak diubah
class PdfFile extends Attachment {
preview() { return `${this.fileName}: Tampilkan halaman 1`; }
}
const attachments = [
new ImageFile("logo.png"),
new PdfFile("manual.pdf"),
new Attachment("memo.txt"),
];
// Body-nya satu baris. Di setiap putaran, file berisi instance dari class yang berbeda
for (const file of attachments) {
console.log(file.preview());
}
// logo.png: Tampilkan thumbnail
// manual.pdf: Tampilkan halaman 1
// memo.txt: Tidak ada preview
memo.txt di putaran ketiga dibuat dari Attachment, jadi preview milik Attachment sendiri yang berjalan. Kalau child class baru lupa mendefinisikan preview, hasilnya diam-diam memakai tampilan bawaan ini tanpa error, jadi setiap kali menambah jenis, cek barisnya di daftar.
Memanggil Kode yang Hanya Dimiliki Satu Jenis — Percabangan dengan instanceof
Berikutnya, baris gambar di daftar butuh tombol crop. cropButton, yang mengembalikan tombol itu, hanya didefinisikan di ImageFile, jadi memanggil file.cropButton() untuk semua item di loop akan melempar TypeError di putaran memo.txt.
instanceof mengembalikan true kalau value di sebelah kiri dibuat dari class di sebelah kanan atau salah satu child class-nya. Cek file instanceof ImageFile, lalu panggil cropButton hanya di putaran yang hasilnya true.
Karena semua class punya preview, method itu bisa langsung dipanggil tanpa pengecekan.
class Attachment {
constructor(fileName) { this.fileName = fileName; }
preview() { return `${this.fileName}: Tidak ada preview`; }
}
class ImageFile extends Attachment {
preview() { return `${this.fileName}: Tampilkan thumbnail`; }
cropButton() { return `${this.fileName}: Tampilkan tombol crop`; } // Hanya ImageFile yang punya
}
const attachments = [new ImageFile("logo.png"), new Attachment("memo.txt")];
for (const file of attachments) {
console.log(file.preview()); // Semua class punya, jadi panggil tanpa pengecekan
if (file instanceof ImageFile) {
console.log(file.cropButton()); // Panggil hanya di putaran ImageFile
}
}
// logo.png: Tampilkan thumbnail
// logo.png: Tampilkan tombol crop
// memo.txt: Tidak ada preview
Baris atas gagal dengan TypeError: file.cropButton is not a function. Kalau preview pun kamu cabangkan dengan instanceof, pengecekannya akan bertambah untuk setiap jenis, sama seperti previewOf. Batasi percabangan instanceof hanya untuk method yang dimiliki sebagian class saja.
Bungkus instanceof dengan Kurung Saat Memakai !
Pada !file instanceof ImageFile, !file dievaluasi lebih dulu dan menjadi false, jadi yang sebenarnya dicek adalah false instanceof ImageFile, yang selalu false. Untuk menyatakan "bukan ImageFile", bungkus dengan kurung: !(file instanceof ImageFile).
Menampilkan Value yang Tidak Mewarisi Class — Duck Typing
Daftar itu juga perlu menampilkan link folder. Link berupa object literal yang punya method preview(), dan tidak mewarisi Attachment. Kalau kamu mengecek instanceof Attachment untuk menyaring data lama yang tidak punya preview, seperti { fileName: "old.doc" }, link-nya juga mendapat false dan ikut masuk ke kelompok "Tidak bisa ditampilkan" bersama data lama.
Dengan duck typing (menentukan cara menangani sebuah value hanya dari ada atau tidaknya method yang dibutuhkan, tanpa memeriksa class atau hubungan inheritance-nya), pengecekannya memakai typeof item.preview === "function".
Pemanggilan method tetap berjalan apa pun class-nya, selama value di sebelah kiri titik punya function dengan nama itu.
class Attachment {
constructor(fileName) { this.fileName = fileName; }
preview() { return `${this.fileName}: Tidak ada preview`; }
}
// Link yang dibuat tanpa class. Tidak mewarisi Attachment
const folderLink = {
fileName: "Folder penawaran",
preview() { return `${this.fileName}: Buka link`; },
};
console.log(folderLink instanceof Attachment); // false
// Tentukan dari ada atau tidaknya preview sebagai function, bukan dari class-nya
for (const item of [new Attachment("memo.txt"), folderLink, { fileName: "old.doc" }]) {
if (typeof item.preview === "function") {
console.log(item.preview()); // memo.txt: Tidak ada preview / Folder penawaran: Buka link
} else {
console.log(`${item.fileName}: Tidak bisa ditampilkan`); // old.doc: Tidak bisa ditampilkan
}
}
Karena tidak ada pengecekan instanceof, value yang function preview-nya tidak mengembalikan string juga ikut lolos. Pastikan setiap value yang dimasukkan ke daftar punya preview() yang mengembalikan string untuk ditampilkan. Tabel di bawah membandingkan tiga cara pemanggilan yang dipakai di artikel ini.
| Cara memanggil | Yang dicek | Kapan dipakai |
|---|---|---|
| Panggil preview() langsung | Tidak ada | Semua element punya preview |
| Cabang dengan instanceof | Dibuat dari ImageFile atau child-nya? | Memanggil method khusus class itu |
| Cabang dengan typeof | Apakah preview sebuah function? | Menerima semua value dengan preview |
in Bernilai true Meskipun preview Bukan Function
"preview" in item bernilai true meskipun value-nya berupa string, seperti pada { preview: "Segera hadir" }. Memanggil item.preview() pada value itu melempar TypeError: item.preview is not a function, jadi sebelum memanggilnya, pakai typeof untuk memastikan value itu memang function.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Untuk memo yang dibuat dari Attachment, apa hasil !memo instanceof ImageFile?
Soal 3Untuk object literal link yang punya method preview(), apa hasil link instanceof Attachment dan typeof link.preview?