Implicit Interface di Go

1. Konsep Dasar Implementasi Interface di Go

  • Implementasi interface bersifat implisit (duck typing):

    • Jika suatu tipe memiliki semua method yang didefinisikan oleh sebuah interface, maka tipe tersebut dianggap mengimplementasikan interface tersebut.

    • Contoh:

      type KeyValueStore interface {
          Get(ctx context.Context, key string) (string, error)
          Set(ctx context.Context, key string, value string) error
      }
      
      type Client struct{}
      func (c *Client) Get(ctx context.Context, key string) (string, error) { ... }
      func (c *Client) Set(ctx context.Context, key string, value string) error { ... }
      • *Client otomatis mengimplementasikan KeyValueStore tanpa deklarasi eksplisit.

2. Kelebihan Implementasi Implisit

  • Tidak perlu ketergantungan (dependency) pada definisi interface:

    • Interface bisa dibuat belakangan tanpa mengubah implementasi.

    • Berguna saat pola desain belum jelas di awal pengembangan.

  • Cocok untuk interface generik (seperti io.Reader, io.Writer):

    • Implementasi tidak perlu mengimpor package yang mendefinisikan interface.

3. Masalah Utama Implementasi Implisit

a. Semantic Meaning yang Tidak Jelas

  • Interface dengan method sama bisa memiliki makna berbeda:

    • Contoh: cache.KeyValueStore (untuk caching) vs. config.KeyValueStore (untuk penyimpanan persistensi).

    • Di Go, *memcached.Client bisa otomatis memenuhi kedua interface, meskipun seharusnya tidak boleh.

    • Bahasa seperti Java/Rust memerlukan deklarasi eksplisit, sehingga mencegah kesalahan semantik.

b. Error Reporting yang Buruk

  • Error muncul di tempat yang salah:

    • Jika interface berubah, error tidak muncul di implementasi, tapi di lokasi penggunaan interface.

    • Contoh:

      // Interface diubah:
      type KeyValueStore interface {
          Get(ctx context.Context, key []byte) ([]byte, error) // sebelumnya string
      }
      
      // Error tidak muncul di struct Client, tapi di fungsi yang memakai Client sebagai KeyValueStore.
    • Membuat debugging lebih sulit karena root cause tidak langsung terlihat.

c. Kebingungan Saat Refaktor

  • Sulit membedakan implementasi disengaja vs. kebetulan:

    • Jika sebuah struct memiliki method yang cocok dengan beberapa interface, tidak jelas mana yang benar-benar dimaksudkan.

    • Contoh:

      • Saat config.KeyValueStore berubah, apakah *memcached.Client harus diupdate?

      • Tanpa deklarasi eksplisit, developer harus menebak berdasarkan konteks.

d. Ketergantungan Package yang Tidak Terhindarkan

  • "Tidak perlu impor interface" hanya berlaku untuk interface sederhana:

    • Jika interface menggunakan custom struct (contoh: auth.User), implementasi tetap harus mengimpor package-nya.

    • Contoh:

      package auth
      type User struct { ... }
      type UserRepository interface {
          InsertUser(ctx context.Context, user User) error // Memaksa impor package auth
      }

4. Rekomendasi

  • Gunakan implementasi implisit hanya untuk interface generik (seperti io.Reader).

  • Untuk interface spesifik:

    • Dokumentasi dengan jelas tujuan interface.

    • Pertimbangkan untuk menambahkan komentar atau prefix/suffix (misal: CacheKeyValueStore vs. ConfigKeyValueStore) untuk membedakan makna.

  • Hati-hati saat refaktor:

    • Periksa semua penggunaan interface untuk memastikan tidak ada implementasi tidak disengaja.

Kesimpulan

  • Implementasi implisit di Go memudahkan prototyping, tetapi berisiko menyebabkan:

    • Kesalahan semantik.

    • Error reporting yang tidak intuitif.

    • Kebingungan saat refaktor.

  • Gunakan dengan bijak:

    • Manfaatkan untuk kasus sederhana/generik.

    • Hindari untuk interface dengan logika kompleks atau makna spesifik.

  • Dokumentasi dan penamaan yang jelas sangat penting untuk mengurangi kebingungan.

Last updated