Foto oleh Jorge Jesus via Pexels

Inheritance (pewarisan) adalah mekanisme OOP yang memungkinkan sebuah class baru (class turunan atau derived class) mewarisi property dan method dari class yang sudah ada (class induk atau base class), memungkinkan penggunaan ulang kode tanpa perlu menulis ulang logika yang sama.

Konsep ini sangat berguna ketika beberapa class memiliki kesamaan karakteristik namun juga memiliki perbedaan tertentu, misalnya class Mobil dan Motor yang sama-sama merupakan Kendaraan namun memiliki jumlah roda dan cara kerja yang berbeda.

Artikel ini membahas cara menerapkan inheritance di C# menggunakan kata kunci base class dan kata kunci override, termasuk penggunaan kata kunci base untuk memanggil implementasi dari class induk, serta kapan inheritance menjadi pilihan desain yang tepat.

Mendefinisikan Base Class dan Derived Class

Mobil mewarisi property dan method dari Kendaraan

public class Kendaraan { public string Merek { get; set; } public virtual void Jalan() => Console.WriteLine("Kendaraan berjalan"); } public class Mobil : Kendaraan { public int JumlahPintu { get; set; } } Mobil mobilSaya = new Mobil { Merek = "Toyota", JumlahPintu = 4 }; mobilSaya.Jalan(); -- Expected: Kendaraan berjalan
Class Mobil yang mewarisi dari Kendaraan secara otomatis mendapatkan property Merek dan method Jalan tanpa perlu mendefinisikannya ulang, sekaligus bisa menambahkan property atau method baru yang spesifik untuk Mobil seperti JumlahPintu yang tidak dimiliki Kendaraan secara umum.

Override Method dengan Kata Kunci virtual dan override

Override mengganti implementasi method dari class induk

public class Mobil : Kendaraan { public override void Jalan() => Console.WriteLine("Mobil melaju di jalan raya"); } Kendaraan k = new Mobil(); k.Jalan(); -- Expected: Mobil melaju di jalan raya (bukan versi Kendaraan)
Method pada base class harus ditandai virtual agar bisa di-override oleh class turunan, dan class turunan menandai implementasi barunya dengan kata kunci override. Bahkan ketika variabel bertipe Kendaraan menyimpan object Mobil, method Jalan yang dipanggil tetap versi Mobil berkat polymorphism yang akan dibahas lebih dalam pada artikel terpisah.

Memanggil Implementasi Base Class dengan base

Kata kunci base memanggil implementasi dari class induk

public class Mobil : Kendaraan { public override void Jalan() { base.Jalan(); // memanggil versi Kendaraan terlebih dahulu Console.WriteLine("...dengan kecepatan tinggi"); } } -- Expected output: -- Kendaraan berjalan -- ...dengan kecepatan tinggi
Kata kunci base memungkinkan class turunan tetap memanfaatkan logika yang sudah ada di class induk sambil menambahkan perilaku tambahan, alih-alih harus menulis ulang seluruh logika tersebut dari awal. Ini berguna ketika perilaku class turunan hanya perlu menambah sedikit hal di atas perilaku dasar yang sudah ada.

Sealed Class untuk Mencegah Pewarisan Lebih Lanjut

Sealed class mencegah class lain mewarisinya

public sealed class MobilListrik : Kendaraan { public int KapasitasBaterai { get; set; } } // class TeslaModel3 : MobilListrik { } -- INI AKAN ERROR // karena MobilListrik ditandai sealed
Kata kunci sealed pada deklarasi class mencegah class lain mewarisi darinya, berguna ketika sebuah class dianggap sudah final dan tidak boleh diturunkan lagi, baik untuk alasan keamanan desain maupun untuk memberi sedikit keuntungan performa karena compiler bisa melakukan optimasi tertentu pada class yang dipastikan tidak memiliki turunan.

Memanggil Method Parent Class dengan Kata Kunci base

Kata kunci base memanggil implementasi method milik parent class

class Kendaraan { public virtual string Deskripsi() => "Kendaraan umum"; } class Mobil : Kendaraan { public override string Deskripsi() { return base.Deskripsi() + " - jenis Mobil"; } } Kendaraan k = new Mobil(); Console.WriteLine(k.Deskripsi()); // Output: Kendaraan umum - jenis Mobil

Selain dipakai untuk memanggil constructor parent, kata kunci base juga dapat dipakai untuk memanggil implementasi method milik parent class dari dalam method yang sama pada class turunan yang sudah di-override. Pada contoh di atas, method Deskripsi() pada class Mobil tidak sepenuhnya menggantikan logika parent, melainkan memperluasnya dengan menambahkan teks tambahan setelah memanggil base.Deskripsi().

Pola ini sangat berguna ketika class turunan ingin menambahkan perilaku baru tanpa menduplikasi seluruh logika yang sudah ada pada parent class, sehingga perubahan pada implementasi parent akan otomatis terbawa ke semua class turunan yang memanggil base tanpa perlu mengubah kode di masing-masing turunan satu per satu.

Inheritance pada C# hanya mendukung single inheritance untuk class, artinya satu class hanya boleh mewarisi dari satu parent class saja, berbeda dengan interface yang bisa diimplementasikan lebih dari satu sekaligus. Keterbatasan ini sengaja dirancang untuk menghindari masalah diamond problem yang umum terjadi pada bahasa yang mendukung multiple inheritance penuh seperti C++.

Sealed Class dan Sealed Method untuk Membatasi Inheritance

Class sealed tidak bisa diturunkan lagi oleh class manapun

public class NotifikasiDasar { public virtual void Kirim() => Console.WriteLine("Kirim notifikasi dasar"); } public sealed class NotifikasiSms : NotifikasiDasar { public override void Kirim() => Console.WriteLine("Kirim via SMS"); } // class NotifikasiSmsPremium : NotifikasiSms { } // Compile error, NotifikasiSms sealed NotifikasiDasar n = new NotifikasiSms(); n.Kirim(); // Output: Kirim via SMS

Kata kunci sealed yang diterapkan pada deklarasi class, seperti NotifikasiSms di atas, mencegah class tersebut diwarisi oleh class lain manapun, sehingga percobaan membuat class turunan darinya akan langsung ditolak compiler pada tahap kompilasi. Pembatasan ini umumnya diterapkan pada class yang desainnya sudah dianggap final dan tidak dimaksudkan untuk diperluas lebih jauh, guna mencegah penyalahgunaan inheritance yang dapat merusak invariant atau asumsi penting yang dijaga oleh class tersebut.

Selain pada level class, kata kunci sealed juga dapat diterapkan pada method hasil override di dalam class turunan, yang berarti method tersebut sudah final pada level hierarki tersebut dan tidak dapat di-override lagi oleh class turunan berikutnya meskipun class induknya sendiri tidak sealed secara keseluruhan. Pola ini memberi kontrol lebih presisi dibanding men-sealed seluruh class, karena hanya method tertentu yang dibatasi sementara method lain tetap bisa di-override oleh turunan selanjutnya.

Dari sisi performa, JIT compiler terkadang dapat melakukan optimisasi tambahan terhadap pemanggilan method pada sealed class, karena kepastian bahwa tidak akan ada override lebih lanjut memungkinkan penghilangan sebagian overhead dynamic dispatch yang biasanya diperlukan untuk method virtual. Class seperti string pada .NET sendiri bersifat sealed, memastikan perilakunya konsisten dan tidak bisa dimodifikasi lewat inheritance oleh kode pihak ketiga manapun. Sealed juga sering dipakai sebagai keputusan desain defensif pada library publik, karena setelah dirilis dan dipakai banyak konsumen, mengizinkan inheritance pada class yang semula sealed jauh lebih aman dilakukan di kemudian hari dibanding mencabut kemampuan inheritance yang sudah terlanjur dipakai oleh banyak kode eksternal.

Perbedaan Virtual dan Abstract Member pada Inheritance

public abstract class Kendaraan { public abstract string Suara(); public virtual string Deskripsi() => "Kendaraan bermotor"; } public class Motor : Kendaraan { public override string Suara() => "Brmm"; } var motor = new Motor(); Console.WriteLine(motor.Suara()); Console.WriteLine(motor.Deskripsi()); // Output: // Brmm // Kendaraan bermotor

Member abstract dideklarasikan tanpa isi implementasi sama sekali dan wajib di-override oleh setiap class turunan non-abstract, seperti method Suara() pada contoh di atas yang memaksa setiap jenis kendaraan mendefinisikan suaranya sendiri sesuai karakteristiknya masing-masing. Class yang memiliki member abstract otomatis harus dideklarasikan sebagai abstract class dan tidak bisa diinstansiasi langsung dengan kata kunci new, karena dianggap belum lengkap implementasinya sampai semua anggota abstract-nya diimplementasikan oleh class turunan.

Member virtual berbeda karena sudah memiliki implementasi default pada class induk, seperti Deskripsi() yang mengembalikan teks umum, dan class turunan boleh memilih untuk meng-override-nya atau tetap memakai perilaku bawaan tanpa dipaksa sama sekali. Pola ini cocok ketika sebagian besar class turunan memiliki perilaku yang sama dan cukup memakai implementasi induk, namun beberapa di antaranya membutuhkan penyesuaian khusus sesuai kebutuhan masing-masing jenis objek turunan tersebut, tanpa harus menulis ulang seluruh logika dari awal.

Kesalahan Umum yang Sering Terjadi

Menggunakan Inheritance untuk Relasi yang Bukan 'Is-A'

Inheritance hanya tepat dipakai ketika hubungan antar class benar-benar bersifat 'is-a', misalnya Mobil adalah Kendaraan; memaksakan inheritance untuk relasi yang sebenarnya lebih cocok sebagai komposisi akan menghasilkan desain yang janggal dan sulit dipelihara.

Lupa Menandai Method dengan virtual di Base Class

Method yang tidak ditandai virtual di base class tidak bisa di-override oleh class turunan meski ditulis dengan kata kunci new, yang perilakunya berbeda (method hiding, bukan overriding sesungguhnya) dan sering membingungkan pemula.

Membuat Hierarki Inheritance Terlalu Dalam

Hierarki inheritance dengan banyak tingkatan (misalnya A mewarisi B, B mewarisi C, C mewarisi D) membuat kode sulit dilacak dan dipahami, karena perilaku sebuah object bisa berasal dari banyak tingkat yang berbeda sekaligus.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Inheritance memberikan penggunaan ulang kode yang kuat untuk class yang memang memiliki hubungan hierarkis jelas, namun ikatan yang terlalu erat antara base class dan derived class bisa membuat perubahan pada base class berdampak luas ke semua turunannya secara tidak terduga.

Sebagai alternatif, komposisi (menggabungkan object lain sebagai property, bukan mewarisinya) sering dianggap lebih fleksibel untuk kasus yang bukan benar-benar relasi is-a, mengikuti prinsip populer 'favor composition over inheritance' dalam desain software modern.

Ringkasan

Inheritance memungkinkan class turunan mewarisi dan memperluas perilaku dari class induk, dengan virtual dan override sebagai mekanisme untuk mengganti implementasi tertentu. Gunakan inheritance hanya untuk relasi is-a yang jelas, dan pertimbangkan komposisi sebagai alternatif ketika hubungan antar class tidak benar-benar bersifat hierarkis.

Seri Belajar C# Fundamental:

← Sebelumnya: Encapsulation dalam OOP C# Secara Mendalam

→ Selanjutnya: Polymorphism dengan Override dan Overload