Interface adalah kontrak yang menyatukan pemanggil dengan implementasi — mekanisme yang kita andalkan sepanjang dua chapter sebelumnya. Namun kontrak yang terlalu luas berubah menjadi beban: implementasi dipaksa menyediakan method yang tidak relevan baginya, dan pemanggil dipaksa bergantung pada method yang tidak pernah dipakainya. Interface Segregation Principle (ISP) — huruf “I” dalam SOLID — menyerang masalah ini dengan satu kalimat tegas: klien tidak boleh dipaksa bergantung pada method yang tidak digunakannya. Chapter ini menunjukkan wujud interface gemuk, cara memecahnya menjadi role interface, dan mengapa interface kecil justru lebih kuat karena bisa dikomposisikan.

Masalah Interface Gemuk #

Interface gemuk (fat interface) biasanya lahir dari niat baik: mengumpulkan semua kemampuan satu domain ke dalam satu kontrak “lengkap”. Perhatikan kontrak perangkat kantor berikut:

php
<?php

interface PerangkatKantor
{
    public function cetak(string $dokumen): void;
    public function pindai(string $dokumen): void;
    public function kirimFaks(string $dokumen, string $nomorTujuan): void;
}

class PrinterSederhana implements PerangkatKantor
{
    public function cetak(string $dokumen): void
    {
        echo "Mencetak: $dokumen\n";
    }

    public function pindai(string $dokumen): void
    {
        throw new BadMethodCallException("Printer ini tidak mendukung pemindaian");
    }

    public function kirimFaks(string $dokumen, string $nomorTujuan): void
    {
        throw new BadMethodCallException("Printer ini tidak mendukung faks");
    }
}

$printer = new PrinterSederhana();
$printer->cetak("Laporan penjualan");

try {
    $printer->pindai("KTP Budi");
} catch (BadMethodCallException $e) {
    echo "Error: ", $e->getMessage(), "\n";
}

Output yang Dihasilkan:

text
Mencetak: Laporan penjualan
Error: Printer ini tidak mendukung pemindaian

Mari kita bedah masalahnya:

  1. Baris 3-8: PerangkatKantor menuntut tiga kemampuan sekaligus — cetak, pindai, dan faks — dari siapa pun yang mengimplementasikannya.
  2. Baris 10-25: PrinterSederhana hanya bisa mencetak, tetapi terpaksa mengimplementasikan pindai() dan kirimFaks() juga. Isinya? Melempar BadMethodCallException — method mati yang hanya menunggu waktu untuk memancing error.
  3. Baris 31-35: pemanggil bisa memanggil method yang dijanjikan kontrak namun tidak didukung perangkatnya, dan error baru muncul saat runtime.

Method yang isinya cuma throw adalah gejala klasik interface pollution: kontrak membohongi kenyataan. Setiap implementasi baru yang tidak punya semua kemampuan akan mengulangi pola yang sama, dan peta error aplikasi menyebar ke tempat-tempat yang tidak seharusnya.

Memecah Berdasarkan Peran #

Solusi ISP adalah memecah kontrak besar menjadi beberapa interface kecil yang masing-masing menggambarkan satu peran (role interface). Penamaannya pun ikut berubah: bukan lagi nama perangkat, melainkan kemampuan yang ditawarkan:

php
<?php

interface BisaMencetak
{
    public function cetak(string $dokumen): void;
}

interface BisaMemindai
{
    public function pindai(string $dokumen): void;
}

class PrinterSederhana implements BisaMencetak
{
    public function cetak(string $dokumen): void
    {
        echo "Mencetak: $dokumen\n";
    }
}

class PrinterMultifungsi implements BisaMencetak, BisaMemindai
{
    public function cetak(string $dokumen): void
    {
        echo "Mencetak: $dokumen\n";
    }

    public function pindai(string $dokumen): void
    {
        echo "Memindai: $dokumen\n";
    }
}

function prosesDokumen(BisaMencetak $perangkat, string $dokumen): void
{
    $perangkat->cetak($dokumen);
}

prosesDokumen(new PrinterSederhana(), "Laporan penjualan");
prosesDokumen(new PrinterMultifungsi(), "Slip gaji");

Output yang Dihasilkan:

text
Mencetak: Laporan penjualan
Mencetak: Slip gaji

Perubahan kuncinya ada pada tiga titik:

  1. Baris 3-11: kontrak dipecah menjadi BisaMencetak dan BisaMemindai. Masing-masing berisi satu kemampuan dengan satu method — tidak ada method asing.
  2. Baris 13-19: PrinterSederhana kini hanya mengimplementasikan BisaMencetak. Tidak ada method mati, tidak ada exception palsu.
  3. Baris 34-37: fungsi prosesDokumen() bertipe parameter BisaMencetak — ia hanya melihat kemampuan yang benar-benar dibutuhkannya, sekecil mungkin. Perangkat multifungsi tetap diterima karena juga memenuhi peran tersebut.

Prinsip di balik pemecahan ini penting untuk dipahami: interface sebaiknya dimiliki oleh konsumen, bukan oleh implementasi. prosesDokumen() butuh mencetak, maka kontrak “bisa mencetak” ditulis dari sudut pandangnya. Class kemudian memilih peran mana yang sanggup dijannya — satu class boleh memakai beberapa peran sekaligus seperti PrinterMultifungsi di atas.

Interface Kecil itu Composable #

Kekuatan interface kecil baru terasa saat kemampuan-kemampuannya digabungkan. Sejak PHP 8.1 kita bisa menuntut kombinasi beberapa interface sekaligus lewat intersection type pada parameter:

php
<?php

interface BisaMencetak
{
    public function cetak(string $dokumen): void;
}

interface BisaMemindai
{
    public function pindai(string $dokumen): void;
}

class PrinterMultifungsi implements BisaMencetak, BisaMemindai
{
    public function cetak(string $dokumen): void
    {
        echo "Mencetak: $dokumen\n";
    }

    public function pindai(string $dokumen): void
    {
        echo "Memindai: $dokumen\n";
    }
}

function duplikatDokumen(BisaMencetak& BisaMemindai $perangkat, string $dokumen): void
{
    $perangkat->pindai($dokumen);
    $perangkat->cetak($dokumen);
}

duplikatDokumen(new PrinterMultifungsi(), "Kontrak kerja");

Output yang Dihasilkan:

text
Memindai: Kontrak kerja
Mencetak: Kontrak kerja

Tipe parameter BisaMencetak& BisaMemindai dibaca sebagai “objek yang memenuhi kedua kontrak sekaligus”. Fungsi duplikatDokumen() dengan begitu mendeklarasikan kebutuhannya secara presisi: menyalin dokumen memang mensyaratkan perangkat bisa memindai dan mencetak. PrinterSederhana akan ditolak oleh engine sejak awal — lebih baik error saat pemeriksaan tipe daripada BadMethodCallException di tengah proses. Mekanisme lengkap intersection type dibahas pada chapter Union, Intersection, dan Nullable Types .

Perhatikan pola yang terbentuk: interface kecil adalah blok bangunan. Kontrak “bisa mencetak” dipakai prosesDokumen(), dipakai lagi bersama kemampuan lain di duplikatDokumen(), dan bisa dipakai ulang di fungsi mana pun nanti. Interface gemuk tidak punya kemampuan ini — sekali Anda bergantung padanya, Anda menyeret seluruh isinya.

Polusi Interface di Dunia Nyata #

Wujud interface gemuk di proyek nyata sering tidak seeksplicit contoh printer. Yang paling sering: sebuah RepositoryInterface raksasa berisi 30-an method (findAll, findById, save, delete, searchByNama, paginate, hingga hitungStatistik), diimplementasi oleh beberapa class, sementara tiap konsumen sebenarnya hanya memakai tiga sampai empat method. Tidak ada yang melempar exception — semua method “berfungsi” — tetapi setiap konsumen kini bergantung pada 26 method yang tak pernah dipanggil, dan setiap implementasi baru harus menulis ulang semuanya.

Standar library PHP sendiri memberi contoh yang baik: Countable hanya menuntut satu method (count()), dan Iterator menuntut lima method yang semuanya relevan bagi siapa pun yang beriterasi. Kedua interface tersebut bertahan puluhan tahun justru karena kecil dan fokus. Sebagai pedoman praktis saat merancang kontrak sendiri:

CiriInterface gemukInterface terpisah
Isi methodbanyak, mencampur beberapa peransedikit, satu peran koheren
Implementasiada method mati atau throwsemua method benar-benar didukung
Ketergantungan konsumenmenyeret method tak dipakaihanya method yang dibutuhkan
Penggunaan ulangsulit — pakai semua atau tidak sama sekalimudah dikombinasikan, termasuk lewat intersection type
Penambahan kemampuaninterface lama diubah, semua implementasi ikut rusakcukup interface baru

Ringkasan #

Interface Segregation menjaga kontrak tetap sehat dengan aturan sederhana: klien hanya boleh melihat method yang memang digunakannya. Interface gemuk dipotong menjadi role interface yang dinamai berdasarkan kemampuan, class memilih peran yang sanggup dipenuhinya, dan konsumen menyusun kebutuhan presisi — termasuk menggabungkan beberapa peran lewat intersection type. Kontrak kecil yang bersih ini juga menjadi bahan bakar utama prinsip SOLID penutup: Dependency Inversion, yang membalik arah ketergantungan aplikasi Anda.

Referensi #