Thursday, December 11, 2025
No menu items!
HomeWeb DevelopmentBelajar Java ScriptMengenal Interface vs Abstract Class: Konsep dan Contoh

Mengenal Interface vs Abstract Class: Konsep dan Contoh

Bayangkan Anda sedang membangun sebuah rumah perangkat lunak: ada rancangan umum, ada juga kerangka utama yang menahan seluruh bangunan. Dalam pemrograman berorientasi objek, “rancangan umum” itu sering kali diwujudkan dengan interface, sementara “kerangka utama” kerap diwakili oleh abstract class. Keduanya sama-sama tidak bisa dijalankan langsung, tetapi berperan penting sebagai fondasi yang menentukan bentuk dan perilaku kode di atasnya.

Namun, di sinilah kebingungan sering muncul. Kapan sebaiknya menggunakan interface, dan kapan lebih tepat memilih abstract class? Mengapa bahasa pemrograman modern menyediakan keduanya, seolah menggandakan fitur dengan tujuan yang mirip? Di satu sisi, interface tampak ideal untuk mendefinisikan kontrak perilaku. Di sisi lain, abstract class menawarkan titik tengah antara konsep dan implementasi.

Artikel ini akan mengajak Anda mengenal perbedaan mendasar antara interface dan abstract class, memahami cara kerjanya, serta melihat contoh penerapannya dalam kode. Dengan begitu, Anda tidak hanya tahu definisinya, tetapi juga dapat menentukan pilihan yang tepat saat merancang arsitektur program Anda sendiri.

Perbedaan Fundamental Interface dan Abstract Class dalam Desain OOP Modern

Dalam OOP modern, interface dan abstract class sama-sama dipakai untuk mendefinisikan kontrak, tapi cara dan fleksibilitasnya berbeda cukup jauh. Interface biasanya dipakai untuk mengatakan “objek ini bisa melakukan apa”, tanpa peduli bagaimana caranya, sementara abstract class lebih cocok untuk “keluarga” objek yang punya dasar perilaku yang mirip. Di banyak framework modern, interface sering jadi tulang punggung arsitektur karena mendukung komposisi dan dependency injection yang lebih rapi. Sementara itu, abstract class berguna ketika kamu ingin menyediakan implementasi default, menyimpan state bersama, atau menaruh protected helper method yang bisa dipakai ulang oleh turunan.

Interface Abstract Class
Hanya kontrak, tanpa state Bisa punya field dan state
Multi-implementasi diperbolehkan Biasanya hanya single inheritance
Cocok untuk kemampuan (ability/behavior) Cocok untuk hierarki “keluarga” kelas
Lebih fleksibel untuk komposisi Lebih kuat untuk reuse dengan template dasar
  • Gunakan interface saat fokus pada perilaku dan kontrak antar modul.
  • Gunakan abstract class saat butuh fondasi bersama dengan logika yang bisa langsung dipakai.
  • Kombinasi keduanya seringkali yang paling praktis: interface sebagai kontrak, abstract class sebagai implementasi dasar.

Kapan Memilih Interface dan Kapan Menggunakan Abstract Class dalam Proyek Nyata

Dalam proyek nyata, interface biasanya terasa pas ketika kamu ingin mendefinisikan “kontrak” perilaku tanpa peduli bagaimana cara implementasinya. Misalnya pada sistem pembayaran, kamu bisa punya PaymentGateway yang harus punya metode pay(), refund(), dan checkStatus() untuk berbagai provider. Di titik ini, yang kamu butuhkan adalah kesepakatan perilaku, bukan pewarisan logika. Interface juga sangat cocok ketika satu class perlu “berperan” sebagai beberapa tipe sekaligus (multiple inheritance of type). Dalam praktik, ini sering muncul di proyek besar yang modular, misalnya microservice atau plugin WordPress yang ingin tetap longgar keterikatannya.

  • Pakai interface saat ingin kontrak perilaku yang jelas, ringan, dan mudah di-mock untuk testing.
  • Pakai abstract class saat butuh blueprint plus sebagian implementasi bersama.
  • Interface enak untuk integrasi eksternal dan plugin.
  • Abstract class enak untuk hierarki fitur internal yang punya logika dasar yang sama.
Situasi Pilihan Ideal
Banyak vendor / adapter Interface
Butuh template + kode bersama Abstract Class
Class perlu beberapa “peran” sekaligus Interface
Ingin satu base kuat untuk diturunkan Abstract Class

Di sisi lain, abstract class lebih cocok dipakai ketika kamu ingin menyiapkan kerangka besar plus sedikit “bawaan” logika yang bisa langsung dipakai oleh turunan. Contoh di aplikasi web: kamu bisa bikin BaseController yang mengurus hal umum seperti validasi request dan response JSON, lalu controller lain cukup extends tanpa harus nulis ulang. Pendekatan ini terasa natural kalau kamu punya pola yang jelas dan stabil di dalam domainmu. Tapi hati-hati: kalau sejak awal kamu terlalu agresif pakai abstract class, struktur warisan bisa jadi kaku dan susah diubah begitu kebutuhan bisnis mulai geser, jadi jangan ragu mengombinasikan interface + abstract class jika memang lebih fleksibel.

Contoh Implementasi Interface dan Abstract Class di Java dan C Sebagai Panduan Praktis

Supaya lebih kebayang, kita bisa bandingkan cara sebuah kontrak perilaku diwakili di Java dan di C. Di Java, interface dan abstract class sudah jadi fitur bawaan, jadi kita cukup mendeklarasikan kata kunci yang tepat dan compiler mengurus sisanya. Sementara di C, tidak ada interface/abstract class secara langsung, jadi kita biasanya mengakalinya dengan struct + function pointer untuk meniru polimorfisme. Contoh sederhana: anggap kita sedang membangun sistem pembayaran; kita ingin punya kontrak umum untuk semua metode pembayaran (misalnya transfer bank dan e-wallet), tapi tiap metode punya cara eksekusi sendiri.

Bahasa Cara Meniru Kontrak Kegunaan Singkat
Java interface & abstract class Kontrak jelas, sintaks rapi
C struct + function pointer Lebih fleksibel tapi manual
  • Contoh interface dan abstract class di Java (tema: Pembayaran):

// Interface sebagai kontrak perilaku
public interface PaymentMethod {
    void pay(double amount);
    String getName();
}

// Abstract class sebagai template dasar
public abstract class AbstractPayment implements PaymentMethod {
    private String name;

    public AbstractPayment(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    // Method non-abstract boleh punya logika default
    protected void logPayment(double amount) {
        System.out.println("Membayar " + amount + " dengan " + name);
    }
}

// Implementasi konkret: Transfer Bank
public class BankTransfer extends AbstractPayment {

    public BankTransfer() {
        super("Bank Transfer");
    }

    @Override
    public void pay(double amount) {
        logPayment(amount);
        System.out.println("Memproses pembayaran via rekening bank...");
    }
}

// Implementasi konkret: E-Wallet
public class EWalletPayment extends AbstractPayment {

    public EWalletPayment() {
        super("E-Wallet");
    }

    @Override
    public void pay(double amount) {
        logPayment(amount);
        System.out.println("Memproses pembayaran via saldo e-wallet...");
    }
}

// Contoh penggunaan
public class PaymentDemo {
    public static void main(String[] args) {
        PaymentMethod bank = new BankTransfer();
        PaymentMethod wallet = new EWalletPayment();

        bank.pay(150000);
        wallet.pay(50000);
    }
}

  • Contoh pendekatan mirip “interface” di C dengan struct dan function pointer:

#include 

// "Interface" PaymentMethod diwakili oleh struct berisi function pointer
typedef struct PaymentMethod {
    void (*pay)(double amount);
    const char* (*getName)(void);
} PaymentMethod;

// Implementasi: BankTransfer
typedef struct {
    PaymentMethod vtable;  // "vtable" sederhana
    const char* name;
} BankTransfer;

void bank_pay(double amount) {
    printf("Membayar %.2f dengan Bank Transfern", amount);
    printf("Memproses pembayaran via rekening bank...n");
}

const char* bank_get_name(void) {
    return "Bank Transfer";
}

BankTransfer create_bank_transfer() {
    BankTransfer bt;
    bt.vtable.pay = bank_pay;
    bt.vtable.getName = bank_get_name;
    bt.name = "Bank Transfer";
    return bt;
}

// Implementasi: EWalletPayment
typedef struct {
    PaymentMethod vtable;
    const char* name;
} EWalletPayment;

void wallet_pay(double amount) {
    printf("Membayar %.2f dengan E-Walletn", amount);
    printf("Memproses pembayaran via saldo e-wallet...n");
}

const char* wallet_get_name(void) {
    return "E-Wallet";
}

EWalletPayment create_ewallet_payment() {
    EWalletPayment ew;
    ew.vtable.pay = wallet_pay;
    ew.vtable.getName = wallet_get_name;
    ew.name = "E-Wallet";
    return ew;
}

// "Client code" yang hanya kenal PaymentMethod
void processPayment(PaymentMethod* method, double amount) {
    printf("Metode: %sn", method->getName());
    method->pay(amount);
}

int main(void) {
    BankTransfer bank = create_bank_transfer();
    EWalletPayment wallet = create_ewallet_payment();

    processPayment((PaymentMethod*)&bank, 200000);
    processPayment((PaymentMethod*)&wallet, 75000);

    return 0;
}

Rekomendasi Pola Desain dan Best Practice Mengombinasikan Interface dan Abstract Class

Sering kali, solusi paling rapi justru muncul ketika interface dan abstract class saling melengkapi, bukan saling menggantikan. Salah satu pola yang cukup nyaman dipakai adalah menjadikan interface sebagai kontrak perilaku, lalu abstract class sebagai tempat logika dasar dan utilitas. Misalnya, di aplikasi pembayaran, kamu bisa punya interface PaymentGateway untuk mendefinisikan metode inti, lalu abstract class BasePaymentGateway yang mengurus hal-hal umum seperti logging, validasi sederhana, atau retry mechanism. Dengan cara ini, setiap implementasi konkrit (misalnya Midtrans, Xendit, atau payment dummy untuk testing) cukup fokus pada perbedaan bisnisnya saja.

  • Gunakan interface saat ingin fleksibilitas tinggi dan memungkinkan satu class mengimplementasikan banyak “peran”.
  • Gunakan abstract class saat ada perilaku dasar yang berulang dan perlu diwariskan.
  • Kombinasikan ketika satu kontrak perilaku dibutuhkan di banyak tempat, tapi kamu juga ingin “starter pack” implementasi yang siap pakai.
  • Hindari warisan berlapis-lapis; kalau hierarchy mulai bikin pusing, pertimbangkan komposisi atau memecah interface jadi lebih kecil.
Pola Interface Abstract Class
Kontrak murni Ya Tidak wajib
Berbagi logika Tidak Ya
Fleksibel di banyak modul Sangat Terbatas
interface PaymentGateway {
    fun charge(amount: Double): Boolean
    fun refund(transactionId: String): Boolean
}

abstract class BasePaymentGateway : PaymentGateway {
    protected fun log(message: String) {
        println("[PaymentLog] $message")
    }

    override fun refund(transactionId: String): Boolean {
        log("Refund transaksi: $transactionId")
        // Default behavior, bisa di-override
        return true
    }
}

class MidtransGateway : BasePaymentGateway() {
    override fun charge(amount: Double): Boolean {
        log("Charge via Midtrans: $amount")
        // Panggil API Midtrans...
        return true
    }
}

In Conclusion

Pada akhirnya, memahami perbedaan antara interface dan abstract class bukan sekadar soal menghafal definisi, tetapi tentang bagaimana keduanya membentuk cara kita merancang solusi. Interface memberi kita kontrak yang jelas dan tegas, sementara abstract class menawarkan kerangka yang bisa diwarisi dan disempurnakan.

Di dunia nyata pengembangan perangkat lunak, keduanya jarang berdiri sendiri. Mereka saling melengkapi, dipilih sesuai kebutuhan arsitektur, skala proyek, dan fleksibilitas yang diinginkan. Kadang kita butuh kebebasan penuh yang ditawarkan interface, kadang kita mengandalkan fondasi kokoh dari abstract class.

Saat Anda menulis baris kode berikutnya, coba ajukan pertanyaan sederhana: “Apakah saya butuh kontrak, kerangka, atau keduanya?” Dari jawaban itulah desain yang bersih dan terstruktur akan lahir. Dan di situlah interface dan abstract class menemukan perannya masing-masing.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments