Interface #

Python tidak memiliki keyword interface seperti Java atau Go. Tapi Python memiliki sesuatu yang lebih fleksibel: tiga cara berbeda untuk mendefinisikan kontrak antara komponen — duck typing, Abstract Base Class (ABC), dan Protocol. Ketiganya menjawab pertanyaan yang sama: “Bagaimana saya memastikan sebuah objek memiliki metode yang saya butuhkan?” — tapi dengan tingkat keketatan dan ekspresi yang berbeda. Memahami kapan menggunakan masing-masing adalah kunci desain kode Python yang bersih dan dapat diperluas.

Duck Typing — Interface Implisit #

Sebelum membahas mekanisme formal, penting untuk memahami filosofi dasar Python: “Jika ia berjalan seperti bebek dan bersuara seperti bebek, maka ia adalah bebek.” Python tidak peduli tipe objek secara eksplisit — yang penting objek memiliki metode yang dibutuhkan.

# Tidak ada deklarasi interface — Python cukup panggil metodenya
class Anjing:
    def bersuara(self):
        return "Guk!"

class Kucing:
    def bersuara(self):
        return "Meow!"

class Bebek:
    def bersuara(self):
        return "Kwek!"

# Fungsi ini bekerja untuk semua objek yang punya metode bersuara()
# Tidak perlu inheritance atau interface deklarasi apapun
def buat_keributan(hewan_list):
    for hewan in hewan_list:
        print(hewan.bersuara())   # Python tidak cek tipe — langsung panggil

hewan = [Anjing(), Kucing(), Bebek()]
buat_keributan(hewan)
# → Guk!
# → Meow!
# → Kwek!

Duck typing sangat kuat dan fleksibel, tapi memiliki kelemahan: tidak ada mekanisme bawaan yang memastikan objek memang memiliki metode yang dibutuhkan sebelum runtime. Error baru muncul saat metode dipanggil — bukan saat objek dibuat.


Abstract Base Class (ABC) #

ABC adalah cara formal mendefinisikan interface di Python. Kelas yang mewarisi ABC dengan metode abstrak wajib mengimplementasikan semua metode abstrak tersebut — jika tidak, Python akan melempar TypeError saat mencoba membuat instance.

ABC Dasar #

from abc import ABC, abstractmethod

class Kendaraan(ABC):
    """Interface untuk semua jenis kendaraan."""

    @abstractmethod
    def bergerak(self) -> str:
        """Mengembalikan deskripsi gerakan kendaraan."""
        ...

    @abstractmethod
    def berhenti(self) -> None:
        """Menghentikan kendaraan."""
        ...

    @abstractmethod
    def kecepatan_maks(self) -> float:
        """Mengembalikan kecepatan maksimum dalam km/h."""
        ...

    # Metode konkret di ABC — tersedia untuk semua subkelas
    def info(self) -> str:
        return f"{self.__class__.__name__}: kecepatan maks {self.kecepatan_maks()} km/h"


# ANTI-PATTERN: subkelas yang tidak mengimplementasikan semua metode abstrak
class Sepeda(Kendaraan):
    def bergerak(self) -> str:
        return "mengayuh pedal"
    # berhenti() dan kecepatan_maks() belum diimplementasikan!

# sepeda = Sepeda()
# → TypeError: Can't instantiate abstract class Sepeda
#   with abstract methods berhenti, kecepatan_maks


# BENAR: implementasikan semua metode abstrak
class Mobil(Kendaraan):
    def __init__(self, merek: str, top_speed: float):
        self.merek = merek
        self._top_speed = top_speed
        self._berjalan = False

    def bergerak(self) -> str:
        self._berjalan = True
        return f"{self.merek} mulai bergerak"

    def berhenti(self) -> None:
        self._berjalan = False

    def kecepatan_maks(self) -> float:
        return self._top_speed


class Sepeda(Kendaraan):
    def bergerak(self) -> str:
        return "Mengayuh pedal"

    def berhenti(self) -> None:
        print("Menarik rem")

    def kecepatan_maks(self) -> float:
        return 30.0


class Kereta(Kendaraan):
    def bergerak(self) -> str:
        return "Melaju di rel"

    def berhenti(self) -> None:
        print("Rem pneumatik aktif")

    def kecepatan_maks(self) -> float:
        return 300.0


# Tidak bisa instantiasi ABC secara langsung
# kendaraan = Kendaraan()  # → TypeError

mobil = Mobil("Toyota", 180.0)
sepeda = Sepeda()
kereta = Kereta()

print(mobil.bergerak())    # → Toyota mulai bergerak
print(mobil.info())        # → Mobil: kecepatan maks 180.0 km/h
print(sepeda.info())       # → Sepeda: kecepatan maks 30.0 km/h
print(kereta.info())       # → Kereta: kecepatan maks 300.0 km/h

# Polimorfisme — fungsi bekerja pada semua Kendaraan
def balapkan(kendaraan_list: list[Kendaraan]) -> None:
    for k in sorted(kendaraan_list, key=lambda x: x.kecepatan_maks(), reverse=True):
        print(f"{k.__class__.__name__}: {k.bergerak()} ({k.kecepatan_maks()} km/h)")

balapkan([mobil, sepeda, kereta])
# → Kereta: Melaju di rel (300.0 km/h)
# → Mobil: Toyota mulai bergerak (180.0 km/h)
# → Sepeda: Mengayuh pedal (30.0 km/h)

Abstract Property #

ABC juga mendukung abstract property — atribut yang wajib diimplementasikan oleh subkelas:

from abc import ABC, abstractmethod

class Bentuk(ABC):
    """Interface untuk bentuk geometri 2D."""

    @property
    @abstractmethod
    def luas(self) -> float:
        """Luas bentuk dalam satuan persegi."""
        ...

    @property
    @abstractmethod
    def keliling(self) -> float:
        """Keliling bentuk."""
        ...

    def deskripsi(self) -> str:
        return (
            f"{self.__class__.__name__}: "
            f"luas={self.luas:.2f}, keliling={self.keliling:.2f}"
        )


class Lingkaran(Bentuk):
    def __init__(self, jari_jari: float):
        self.jari_jari = jari_jari

    @property
    def luas(self) -> float:
        import math
        return math.pi * self.jari_jari ** 2

    @property
    def keliling(self) -> float:
        import math
        return 2 * math.pi * self.jari_jari


class Persegi(Bentuk):
    def __init__(self, sisi: float):
        self.sisi = sisi

    @property
    def luas(self) -> float:
        return self.sisi ** 2

    @property
    def keliling(self) -> float:
        return 4 * self.sisi


class SegiTiga(Bentuk):
    def __init__(self, alas: float, tinggi: float, sisi_a: float, sisi_b: float, sisi_c: float):
        self.alas = alas
        self.tinggi = tinggi
        self._sisi = (sisi_a, sisi_b, sisi_c)

    @property
    def luas(self) -> float:
        return 0.5 * self.alas * self.tinggi

    @property
    def keliling(self) -> float:
        return sum(self._sisi)


bentuk_list: list[Bentuk] = [
    Lingkaran(7),
    Persegi(5),
    SegiTiga(6, 4, 6, 5, 5),
]

for bentuk in bentuk_list:
    print(bentuk.deskripsi())
# → Lingkaran: luas=153.94, keliling=43.98
# → Persegi: luas=25.00, keliling=20.00
# → SegiTiga: luas=12.00, keliling=16.00

ABC dengan Implementasi Default (Template Method Pattern) #

ABC tidak harus hanya berisi metode abstrak — bisa menyediakan implementasi default yang bisa di-override oleh subkelas:

from abc import ABC, abstractmethod

class Laporan(ABC):
    """Template untuk pembuatan laporan — pola Template Method."""

    def buat(self) -> str:
        """Urutan langkah sudah ditentukan, detail diimplementasikan subkelas."""
        bagian = [
            self._header(),
            self._isi(),
            self._footer(),
        ]
        return "\n".join(bagian)

    @abstractmethod
    def _header(self) -> str:
        ...

    @abstractmethod
    def _isi(self) -> str:
        ...

    def _footer(self) -> str:
        # Implementasi default — subkelas boleh override atau tidak
        return "--- Akhir Laporan ---"


class LaporanPenjualan(Laporan):
    def __init__(self, bulan: str, total: float):
        self.bulan = bulan
        self.total = total

    def _header(self) -> str:
        return f"=== LAPORAN PENJUALAN {self.bulan.upper()} ==="

    def _isi(self) -> str:
        return f"Total penjualan: Rp{self.total:,.0f}"


class LaporanInventaris(Laporan):
    def __init__(self, produk_list: list):
        self.produk_list = produk_list

    def _header(self) -> str:
        return "=== LAPORAN INVENTARIS ==="

    def _isi(self) -> str:
        baris = [f"- {p['nama']}: {p['stok']} unit" for p in self.produk_list]
        return "\n".join(baris)

    def _footer(self) -> str:
        # Override footer untuk laporan inventaris
        total_stok = sum(p['stok'] for p in self.produk_list)
        return f"Total stok keseluruhan: {total_stok} unit"


lap_jual = LaporanPenjualan("Maret", 125_500_000)
print(lap_jual.buat())
# → === LAPORAN PENJUALAN MARET ===
# → Total penjualan: Rp125,500,000
# → --- Akhir Laporan ---

Virtual Subclass — Daftarkan Tanpa Mewarisi #

ABC juga mendukung virtual subclass — kelas yang dianggap sebagai implementasi ABC tanpa mewarisinya secara langsung. Berguna untuk mengintegrasikan kelas pihak ketiga ke dalam sistem tipe yang sudah ada.

from abc import ABC, abstractmethod

class Serializable(ABC):
    @abstractmethod
    def serialize(self) -> str:
        ...

    @abstractmethod
    def deserialize(self, data: str) -> None:
        ...

# Kelas pihak ketiga yang tidak mewarisi Serializable
class KelasLuarBiasa:
    def serialize(self) -> str:
        return '{"data": "ok"}'

    def deserialize(self, data: str) -> None:
        pass

# Daftarkan sebagai virtual subclass
Serializable.register(KelasLuarBiasa)

obj = KelasLuarBiasa()
print(isinstance(obj, Serializable))   # → True  (meski tidak mewarisi)
print(issubclass(KelasLuarBiasa, Serializable))   # → True

Protocol — Structural Subtyping (Duck Typing Formal) #

Protocol (diperkenalkan di Python 3.8 via PEP 544) adalah cara mendefinisikan interface berdasarkan struktur — bukan berdasarkan pewarisan. Kelas dianggap mengimplementasikan Protocol selama ia memiliki metode dan atribut yang diperlukan, tanpa perlu mewarisi atau mendaftarkan diri secara eksplisit.

from typing import Protocol, runtime_checkable

class Drawable(Protocol):
    """Protocol untuk objek yang bisa digambar."""
    def draw(self) -> None:
        ...

    def resize(self, factor: float) -> None:
        ...


# Kelas-kelas berikut TIDAK mewarisi Drawable — tapi dianggap mengimplementasikannya
# karena memiliki metode draw() dan resize()
class Lingkaran:
    def __init__(self, r: float):
        self.r = r

    def draw(self) -> None:
        print(f"Menggambar lingkaran r={self.r}")

    def resize(self, factor: float) -> None:
        self.r *= factor


class Kotak:
    def __init__(self, w: float, h: float):
        self.w = w
        self.h = h

    def draw(self) -> None:
        print(f"Menggambar kotak {self.w}x{self.h}")

    def resize(self, factor: float) -> None:
        self.w *= factor
        self.h *= factor


# Type checker (mypy) akan menerima keduanya sebagai Drawable
def render_semua(shapes: list[Drawable]) -> None:
    for shape in shapes:
        shape.draw()

shapes = [Lingkaran(5), Kotak(10, 8)]
render_semua(shapes)
# → Menggambar lingkaran r=5
# → Menggambar kotak 10x8

@runtime_checkable — Protocol yang Bisa Dicek dengan isinstance #

from typing import Protocol, runtime_checkable

@runtime_checkable
class Dapat_Ditutup(Protocol):
    def close(self) -> None:
        ...


class KoneksiDatabase:
    def close(self) -> None:
        print("Menutup koneksi database")


class FileHandler:
    def close(self) -> None:
        print("Menutup file")


class Timer:
    def mulai(self) -> None:  # tidak punya close()
        pass


db = KoneksiDatabase()
fh = FileHandler()
tm = Timer()

print(isinstance(db, Dapat_Ditutup))   # → True
print(isinstance(fh, Dapat_Ditutup))   # → True
print(isinstance(tm, Dapat_Ditutup))   # → False

# Berguna untuk resource cleanup
def tutup_jika_bisa(obj: object) -> None:
    if isinstance(obj, Dapat_Ditutup):
        obj.close()

tutup_jika_bisa(db)   # → Menutup koneksi database
tutup_jika_bisa(tm)   # → (tidak ada output — Timer tidak punya close())

ABC vs Protocol — Kapan Menggunakan Yang Mana #

Gunakan ABC jika:
  ✓ Ingin memaksa implementasi metode saat kelas dibuat (bukan saat dipakai)
  ✓ ABC menyediakan implementasi default yang dibagikan ke subkelas
  ✓ Hubungan IS-A yang jelas dan eksplisit diperlukan
  ✓ Ingin menggunakan Template Method pattern
  ✓ Perlu virtual subclass untuk kelas pihak ketiga

Gunakan Protocol jika:
  ✓ Ingin duck typing yang terdokumentasi dan bisa dicek oleh type checker
  ✓ Kelas implementasi sudah ada dan tidak bisa diubah (tidak bisa tambahkan inheritance)
  ✓ Tidak perlu implementasi default — hanya mendefinisikan kontrak
  ✓ Ingin interface yang lebih ringan tanpa coupling ke hirarki kelas
  ✓ Bekerja dengan library pihak ketiga yang tidak bisa diubah
# Perbandingan langsung: ABC vs Protocol untuk interface yang sama

# === Dengan ABC ===
from abc import ABC, abstractmethod

class PenyimpananABC(ABC):
    @abstractmethod
    def simpan(self, kunci: str, nilai: str) -> None: ...
    @abstractmethod
    def ambil(self, kunci: str) -> str | None: ...

class RedisStorage(PenyimpananABC):  # HARUS mewarisi PenyimpananABC
    def simpan(self, kunci, nilai): ...
    def ambil(self, kunci): ...

# === Dengan Protocol ===
from typing import Protocol

class PenyimpananProtocol(Protocol):
    def simpan(self, kunci: str, nilai: str) -> None: ...
    def ambil(self, kunci: str) -> str | None: ...

class RedisStorage:  # tidak perlu deklarasi apapun
    def simpan(self, kunci, nilai): ...
    def ambil(self, kunci): ...

# RedisStorage sudah kompatibel dengan PenyimpananProtocol
# tanpa mewarisi apapun — type checker akan memverifikasi strukturnya

Ringkasan #

  • Duck typing adalah default Python — jika objek memiliki metode yang dibutuhkan, ia bisa digunakan tanpa deklarasi formal apapun.
  • ABC memberikan kontrak eksplisit yang di-enforce saat kelas dibuat — subkelas yang tidak mengimplementasikan metode abstrak akan TypeError saat diinstansiasi, bukan saat metode dipanggil.
  • @abstractmethod menandai metode yang wajib diimplementasikan. @property + @abstractmethod untuk atribut wajib.
  • ABC boleh memiliki implementasi default — subkelas bisa mewarisi atau meng-override. Pola ini disebut Template Method.
  • Virtual subclass (ABC.register()) memungkinkan kelas pihak ketiga dianggap sebagai implementasi ABC tanpa mengubah kodenya.
  • Protocol mendefinisikan interface berdasarkan struktur, bukan pewarisan — kelas apapun yang memiliki metode yang diperlukan dianggap kompatibel.
  • @runtime_checkable memungkinkan isinstance() bekerja pada Protocol — berguna untuk resource cleanup dan dispatch dinamis.
  • Pilih ABC jika butuh enforcement saat kelas didefinisikan atau implementasi default dishare. Pilih Protocol jika butuh interface yang lebih ringan dan tidak ingin coupling ke hirarki kelas.

← Sebelumnya: Kelas   Berikutnya: Eksepsi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact