Abstract class dan interface sama-sama menunda isi ke class lain, dan keduanya tidak dapat diinstansiasi langsung. Persamaan itu sering membuat pemula sulit memilih. Keduanya sebenarnya dibangun untuk kebutuhan berbeda: abstract class berbagi data dan kode di dalam satu hierarki, sedangkan interface menjanjikan kemampuan lintas hierarki. Bab ini merangkum perbedaannya, lalu menunjukkan cara memadukan keduanya.
Perbedaan Pokok #
| Aspek | Abstract Class | Interface |
|---|---|---|
| Field instance | Boleh disimpan | Tidak boleh |
| Constructor | Boleh, untuk inisialisasi turunan | Tidak punya |
| Member dengan badan | Bebas ditulis | Hanya melalui default methods |
| Access modifier member | Bebas: public, protected, private | Implisit public |
| Jumlah yang ditanggung class | Hanya satu base class | Banyak interface sekaligus |
| State antar object | Tersimpan di field | Tidak memiliki state |
Aturan praktisnya: bila turunan butuh berbagi field dan method konkret, gunakan abstract class; bila kemampuan harus dapat dimiliki class dari hierarki mana pun, gunakan interface.
Memadukan Keduanya #
Class boleh mewarisi satu abstract class sekaligus mengimplementasikan interface. Contoh berikut membagi tugas: abstract class menyimpan Nama dan helper Deskripsi, sedangkan interface menjanjikan kontrak pembayaran:
public abstract class MetodePembayaran
{
public string Nama { get; }
protected MetodePembayaran(string nama)
{
Nama = nama;
}
public abstract int BiayaAdmin { get; }
public string Deskripsi()
{
return $"{Nama} (biaya admin Rp{BiayaAdmin})";
}
}
public class KartuDebit : MetodePembayaran, IPembayaran
{
public KartuDebit() : base("Kartu Debit")
{
}
public override int BiayaAdmin => 0;
public void ProsesPembayaran(int jumlah)
{
Console.WriteLine($"Kartu Debit: pembayaran Rp{jumlah} diproses.");
}
}
public class TransferInstan : MetodePembayaran, IPembayaran
{
public TransferInstan() : base("Transfer Instan")
{
}
public override int BiayaAdmin => 6500;
public void ProsesPembayaran(int jumlah)
{
Console.WriteLine($"Transfer Instan: pembayaran Rp{jumlah} diproses.");
}
}Pemanggil kemudian menempelkan kedua class ke dalam daftar bertipe interface:
List<IPembayaran> metode = new List<IPembayaran>
{
new KartuDebit(),
new TransferInstan()
};
foreach (IPembayaran item in metode)
{
MetodePembayaran dasar = (MetodePembayaran)item;
Console.WriteLine(dasar.Deskripsi());
item.ProsesPembayaran(200000);
}Output yang Dihasilkan:
Kartu Debit (biaya admin Rp0)
Kartu Debit: pembayaran Rp200000 diproses.
Transfer Instan (biaya admin Rp6500)
Transfer Instan: pembayaran Rp200000 diproses.Mari kita bedah bagian pentingnya:
public abstract int BiayaAdmin { get; }— deklarasi abstract ini sekaligus menepati kontrakIPembayaran.BiayaAdmin; setiap turunan cukup menulisoverridesatu kali untuk dua kepentingan.public string Deskripsi()— member konkret di abstract class langsung dipakai seluruh turunan tanpa ditulis ulang.MetodePembayaran dasar = (MetodePembayaran)item;— cast ini valid karena setiap object dalam daftar adalah turunanMetodePembayaran, meskipun variabelnya bertipeIPembayaran.override int BiayaAdmin— tiap class mengisi nilai biayanya sendiri, dan hasilnya terbaca baik lewat kontrak interface maupun base class.new KartuDebit()— constructor public pada turunan memanggilbase("Kartu Debit"); constructor pada abstract class hanya dapat dipanggil oleh turunannya.
Pemisahan peran ini menjaga desain tetap rapi: data dan helper bersama tinggal di abstract class, sedangkan janji kemampuan tetap dapat dirakit lintas hierarki melalui interface.