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 #

AspekAbstract ClassInterface
Field instanceBoleh disimpanTidak boleh
ConstructorBoleh, untuk inisialisasi turunanTidak punya
Member dengan badanBebas ditulisHanya melalui default methods
Access modifier memberBebas: public, protected, privateImplisit public
Jumlah yang ditanggung classHanya satu base classBanyak interface sekaligus
State antar objectTersimpan di fieldTidak 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:

csharp
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:

csharp
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:

text
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:

  1. public abstract int BiayaAdmin { get; } — deklarasi abstract ini sekaligus menepati kontrak IPembayaran.BiayaAdmin; setiap turunan cukup menulis override satu kali untuk dua kepentingan.
  2. public string Deskripsi() — member konkret di abstract class langsung dipakai seluruh turunan tanpa ditulis ulang.
  3. MetodePembayaran dasar = (MetodePembayaran)item; — cast ini valid karena setiap object dalam daftar adalah turunan MetodePembayaran, meskipun variabelnya bertipe IPembayaran.
  4. override int BiayaAdmin — tiap class mengisi nilai biayanya sendiri, dan hasilnya terbaca baik lewat kontrak interface maupun base class.
  5. new KartuDebit() — constructor public pada turunan memanggil base("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.