Interface #

Interface di Go adalah fitur yang paling membingungkan bagi developer yang datang dari Java atau C# — bukan karena susah, tapi karena terlalu sederhana dibanding ekspektasi. Di Java, kamu harus mendeklarasikan implements Runnable secara eksplisit. Di Go, tidak ada deklarasi seperti itu sama sekali. Sebuah tipe otomatis memenuhi interface jika ia memiliki semua method yang interface syaratkan. Ini bukan kelemahan desain — ini adalah kekuatan terbesar interface Go, yang membuat decoupling antar komponen menjadi sangat alami dan membuat testing menjadi jauh lebih mudah.

Interface Mendefinisikan Kontrak Perilaku #

Interface di Go hanya mendefinisikan apa yang bisa dilakukan — bukan apa yang dimiliki. Tidak ada field, tidak ada implementasi, hanya daftar method signature:

// Interface mendefinisikan kontrak: "apapun yang bisa di-write"
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Interface mendefinisikan kontrak: "apapun yang bisa di-close"
type Closer interface {
    Close() error
}

// Interface mendefinisikan kontrak: "apapun yang punya representasi string"
type Stringer interface {
    String() string
}

// Interface yang lebih kaya
type Shape interface {
    Area() float64
    Perimeter() float64
    String() string
}

Perhatikan tidak ada keyword public, abstract, atau virtual. Hanya daftar method dan tipe-nya.


Implicit Implementation — Kekuatan Terbesar Go #

Di Java: class MyWriter implements Writer { ... } — kamu harus menyatakan bahwa kamu mengimplementasikan interface.

Di Go: tidak ada deklarasi apapun. Compiler memeriksa sendiri apakah sebuah tipe memenuhi semua method yang diminta interface:

type Writer interface {
    Write(p []byte) (n int, err error)
}

// FileWriter mengimplementasikan Writer — tanpa "implements Writer"!
type FileWriter struct {
    path string
    f    *os.File
}

func (fw *FileWriter) Write(p []byte) (int, error) {
    return fw.f.Write(p)
}

// NetworkWriter juga mengimplementasikan Writer — sama, tanpa deklarasi
type NetworkWriter struct {
    conn net.Conn
}

func (nw *NetworkWriter) Write(p []byte) (int, error) {
    return nw.conn.Write(p)
}

// Fungsi ini menerima APAPUN yang bisa di-write
func saveData(w Writer, data []byte) error {
    _, err := w.Write(data)
    return err
}

func main() {
    fw := &FileWriter{path: "output.txt"}
    nw := &NetworkWriter{}

    saveData(fw, []byte("data ke file"))     // ✓
    saveData(nw, []byte("data ke network"))  // ✓ — polimorfisme!
}

Mengapa Implicit Implementation Sangat Kuat #

Bayangkan kamu menggunakan library pihak ketiga yang mendefinisikan struct ExternalDB. Library itu tidak tahu tentang interface UserRepository yang kamu buat. Tapi selama ExternalDB memiliki method yang interface-mu butuhkan, ia otomatis memenuhinya — tanpa kamu perlu mengubah library tersebut, tanpa perlu wrapper class.

// Interface-mu di package aplikasi
type UserRepository interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

// Library pihak ketiga — kamu tidak bisa mengubah kode ini
// tapi ia memiliki method FindByID dan Save
type ThirdPartyDB struct { ... }
func (db *ThirdPartyDB) FindByID(id int) (*User, error) { ... }
func (db *ThirdPartyDB) Save(user *User) error { ... }

// ThirdPartyDB otomatis memenuhi UserRepository
// tanpa perlu mengubah ThirdPartyDB!
var repo UserRepository = &ThirdPartyDB{}

Interface Value — Dua Komponen Internal #

Penting dipahami: sebuah variabel interface menyimpan dua hal sekaligus — tipe konkret dan nilai konkretnya. Ini mempengaruhi bagaimana nil interface bekerja:

// Representasi internal interface value:
// ┌──────────────┬───────────────┐
// │  type (T)    │  value (V)    │
// └──────────────┴───────────────┘

var w Writer         // type=nil, value=nil → interface nil

var fw *FileWriter = nil
w = fw               // type=*FileWriter, value=nil → interface TIDAK nil!

fmt.Println(w == nil)  // false! — meski fw adalah nil pointer

Ini adalah gotcha yang sangat terkenal di Go — nil interface berbeda dari interface yang berisi nil pointer:

// ANTI-PATTERN: fungsi ini tidak berperilaku seperti yang diharapkan
func getWriter(useFile bool) Writer {
    var fw *FileWriter  // nil pointer
    if useFile {
        fw = openFile()
    }
    return fw  // mengembalikan Writer yang berisi (*FileWriter, nil)
               // bukan nil Writer!
}

func main() {
    w := getWriter(false)
    if w == nil {
        fmt.Println("tidak ada writer")  // TIDAK pernah tercetak!
    }
    // w != nil meski fw adalah nil pointer
    // Memanggil w.Write() akan panic karena fw adalah nil
}

// BENAR: return nil interface secara eksplisit
func getWriter(useFile bool) Writer {
    if useFile {
        return openFile()  // *FileWriter yang valid
    }
    return nil  // nil interface yang sesungguhnya
}
Jangan pernah return variabel interface yang mungkin nil dari fungsi. Selalu return nil secara eksplisit jika tidak ada implementasi. Mengembalikan (*ConcreteType)(nil) dikemas dalam interface akan menghasilkan interface yang tidak nil, menyebabkan pengecekan if result == nil selalu false.

Interface Kecil Lebih Kuat #

Salah satu prinsip terpenting di komunitas Go: interface kecil lebih berguna dari interface besar. io.Reader hanya punya satu method — tapi ia dipakai di ribuan tempat di ekosistem Go:

// io.Reader — satu method, ribuan implementasi
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Semua ini mengimplementasikan io.Reader:
// - *os.File
// - *bytes.Buffer
// - *strings.Reader
// - net.Conn
// - *http.Request.Body
// - *gzip.Reader
// - *zip.Reader
// ...dan ratusan lainnya

// Fungsi yang menerima io.Reader bekerja dengan SEMUA implementasi di atas
func countLines(r io.Reader) (int, error) {
    scanner := bufio.NewScanner(r)
    count := 0
    for scanner.Scan() {
        count++
    }
    return count, scanner.Err()
}

// Bisa dipakai dengan file, string, network connection, dll
lines, _ := countLines(os.Stdin)
lines, _ = countLines(strings.NewReader("baris 1\nbaris 2\n"))
lines, _ = countLines(httpResp.Body)
Panduan ukuran interface:

  1-2 method  ✓ Sangat bagus — bisa diimplementasikan oleh banyak tipe
  3-5 method  ✓ Masih OK — kontrak yang jelas dan terbatas
  6-10 method ⚠ Mulai berat — pertimbangkan pecah jadi interface kecil
  10+ method  ✗ Hampir pasti terlalu besar — sulit di-mock, sulit dipakai

Komposisi Interface #

Interface bisa di-embed ke interface lain untuk membentuk kontrak yang lebih besar, persis seperti struct embedding:

// Interface atomik — masing-masing satu kemampuan
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

// Komposisi — dibuat dari interface yang lebih kecil
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

type ReadWriteSeeker interface {
    Reader
    Writer
    Seeker
}

// Tipe yang mengimplementasikan ReadWriteCloser otomatis
// juga mengimplementasikan Reader, Writer, dan Closer secara terpisah

Komposisi ini memungkinkan kamu memilih kontrak yang tepat untuk setiap fungsi — berikan hanya apa yang dibutuhkan, tidak lebih:

func processInput(r Reader) { ... }      // hanya butuh Read
func writeOutput(w Writer) { ... }       // hanya butuh Write
func handleConn(rwc ReadWriteCloser) { ... }  // butuh ketiganya

any dan interface{} #

interface{} (atau alias any sejak Go 1.18) adalah interface kosong — memenuhi semua tipe karena tidak mensyaratkan method apapun:

// any dan interface{} adalah identik — any hanyalah alias
var v any = 42
v = "hello"
v = []int{1, 2, 3}
v = struct{ X int }{X: 10}

// Berguna untuk generic containers sebelum generics (Go 1.18)
type Stack struct {
    items []any
}

func (s *Stack) Push(item any) {
    s.items = append(s.items, item)
}

func (s *Stack) Pop() (any, bool) {
    if len(s.items) == 0 {
        return nil, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

Gunakan any dengan sangat hemat. Setiap kali kamu menggunakan any, kamu kehilangan type safety yang Go berikan — compiler tidak bisa membantu mendeteksi error tipe saat kompilasi. Sejak Go 1.18, gunakan generics sebagai alternatif yang type-safe untuk container generik:

// ANTI-PATTERN: kehilangan type safety
func contains(slice []any, item any) bool { ... }
items := []any{1, 2, 3}

// BENAR sejak Go 1.18: type-safe dengan generics
func contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item { return true }
    }
    return false
}
fmt.Println(contains([]int{1, 2, 3}, 2))     // true, type-safe
fmt.Println(contains([]string{"a", "b"}, "c")) // false, type-safe

Type Assertion #

Type assertion mengekstrak nilai konkret dari variabel interface:

var i interface{} = "Hello, Go!"

// Safe form — selalu gunakan ini
str, ok := i.(string)
if ok {
    fmt.Println(str)        // Hello, Go!
    fmt.Println(len(str))   // 10
} else {
    fmt.Println("bukan string")
}

// Unsafe form — PANIC jika tipe tidak sesuai
str2 := i.(string)   // OK karena i memang string
num  := i.(int)      // PANIC: interface conversion: interface {} is string, not int

Type assertion sangat berguna untuk mengecek kemampuan tambahan dari nilai yang diterima sebagai interface:

type Writer interface {
    Write([]byte) (int, error)
}

// Cek apakah writer juga bisa di-close
func writeAndClose(w Writer, data []byte) error {
    if _, err := w.Write(data); err != nil {
        return err
    }

    // Type assertion untuk cek kemampuan Close
    if closer, ok := w.(io.Closer); ok {
        return closer.Close()  // panggil Close jika ada
    }
    return nil  // tidak apa-apa jika tidak ada Close
}

// Cek apakah error punya informasi tambahan
func handleError(err error) {
    // Cek apakah error adalah tipe tertentu
    var netErr *net.OpError
    if errors.As(err, &netErr) {
        fmt.Println("network error pada operasi:", netErr.Op)
        return
    }

    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        fmt.Println("path error pada:", pathErr.Path)
        return
    }

    fmt.Println("error umum:", err)
}

Type Switch #

Type switch adalah cara elegan untuk menangani berbagai kemungkinan tipe dalam satu blok — jauh lebih bersih dari serangkaian type assertion:

func formatValue(v any) string {
    switch val := v.(type) {
    case nil:
        return "<nil>"
    case bool:
        if val {
            return "true"
        }
        return "false"
    case int:
        return strconv.Itoa(val)
    case int64:
        return strconv.FormatInt(val, 10)
    case float64:
        return strconv.FormatFloat(val, 'f', -1, 64)
    case string:
        return fmt.Sprintf("%q", val)
    case []byte:
        return fmt.Sprintf("bytes(%d)", len(val))
    case error:
        return "error: " + val.Error()
    case fmt.Stringer:
        // Tipe yang mengimplementasikan Stringer
        return val.String()
    default:
        return fmt.Sprintf("%T(%v)", val, val)
    }
}

func main() {
    fmt.Println(formatValue(nil))          // <nil>
    fmt.Println(formatValue(true))         // true
    fmt.Println(formatValue(42))           // 42
    fmt.Println(formatValue(3.14))         // 3.14
    fmt.Println(formatValue("hello"))      // "hello"
    fmt.Println(formatValue([]byte{1,2}))  // bytes(2)
}

Interface untuk Dependency Injection dan Testing #

Ini adalah penggunaan terpenting interface di kode produksi. Dengan mendefinisikan dependency sebagai interface, kamu bisa:

  1. Mengganti implementasi tanpa mengubah kode yang menggunakannya
  2. Menyuntikkan mock saat testing tanpa library eksternal apapun
// Definisikan dependency sebagai interface di sisi konsumer
type EmailSender interface {
    Send(to, subject, body string) error
}

type SMSSender interface {
    Send(to, message string) error
}

type UserRepository interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

// Service bergantung pada interface, bukan implementasi konkret
type UserService struct {
    repo  UserRepository
    email EmailSender
    sms   SMSSender
}

func NewUserService(repo UserRepository, email EmailSender, sms SMSSender) *UserService {
    return &UserService{repo: repo, email: email, sms: sms}
}

func (s *UserService) Register(name, emailAddr, phone string) error {
    user := &User{Name: name, Email: emailAddr, Phone: phone}

    if err := s.repo.Save(user); err != nil {
        return fmt.Errorf("gagal menyimpan user: %w", err)
    }

    // Kirim notifikasi — tidak peduli implementasinya
    if err := s.email.Send(emailAddr, "Selamat Datang!", "Akun kamu berhasil dibuat."); err != nil {
        return fmt.Errorf("gagal kirim email: %w", err)
    }

    return nil
}

// ── Implementasi nyata untuk produksi ────────────────────────
type SMTPEmailSender struct {
    host string
    port int
}

func (s *SMTPEmailSender) Send(to, subject, body string) error {
    // kirim email via SMTP
    fmt.Printf("[SMTP] Mengirim ke %s: %s\n", to, subject)
    return nil
}

type TwilioSMSSender struct {
    apiKey string
}

func (t *TwilioSMSSender) Send(to, message string) error {
    // kirim SMS via Twilio API
    return nil
}

// ── Mock untuk testing — tanpa framework apapun! ─────────────
type MockEmailSender struct {
    SentEmails []struct{ To, Subject, Body string }
    ShouldFail bool
}

func (m *MockEmailSender) Send(to, subject, body string) error {
    if m.ShouldFail {
        return errors.New("mock: email gagal dikirim")
    }
    m.SentEmails = append(m.SentEmails, struct{ To, Subject, Body string }{to, subject, body})
    return nil
}

type MockUserRepo struct {
    Users  map[int]*User
    SaveErr error
}

func (r *MockUserRepo) FindByID(id int) (*User, error) {
    user, ok := r.Users[id]
    if !ok {
        return nil, fmt.Errorf("user %d tidak ditemukan", id)
    }
    return user, nil
}

func (r *MockUserRepo) Save(user *User) error {
    if r.SaveErr != nil {
        return r.SaveErr
    }
    if r.Users == nil {
        r.Users = make(map[int]*User)
    }
    r.Users[len(r.Users)+1] = user
    return nil
}

Interface di Sisi Konsumer, Bukan Produsen #

Ini adalah prinsip desain paling penting yang sering dilanggar:

// ANTI-PATTERN: interface didefinisikan di sisi PRODUSEN
// package userservice
type UserServiceInterface interface {
    CreateUser(name, email string) (*User, error)
    GetUser(id int) (*User, error)
    UpdateUser(id int, data UpdateData) error
    DeleteUser(id int) error
    ListUsers(filter Filter) ([]*User, error)
    // ... 10 method lagi
}
type UserService struct { ... }
// UserService mengimplementasikan UserServiceInterface

// Masalah: konsumer yang hanya butuh GetUser terpaksa bergantung
// pada interface raksasa ini, dan mock-nya harus mengimplementasikan
// semua 15 method meski hanya 1 yang dipakai

// ─────────────────────────────────────────────────────────────

// BENAR: interface didefinisikan di sisi KONSUMER
// package handler — hanya definisikan yang dibutuhkan
type UserGetter interface {
    GetUser(id int) (*User, error)
}

type ProfileHandler struct {
    users UserGetter  // interface kecil, mudah di-mock
}

// package ordersvc — kebutuhan berbeda, interface berbeda
type UserValidator interface {
    GetUser(id int) (*User, error)
}

type OrderService struct {
    users UserValidator
}

Hasilnya: UserService (struct nyata) otomatis memenuhi UserGetter dan UserValidator karena keduanya hanya butuh satu method yang sama. Setiap konsumer mendapat interface sekecil yang mereka butuhkan.


Method Set — Value vs Pointer #

Ada aturan penting tentang method set yang menentukan kapan tipe value dan pointer bisa memenuhi interface:

type Animal interface {
    Sound() string
    Move()
}

type Dog struct{ Name string }

func (d Dog)  Sound() string { return "Woof" }  // value receiver
func (d *Dog) Move()         { fmt.Println(d.Name, "berlari") }  // pointer receiver

func main() {
    // *Dog mengimplementasikan Animal — pointer memiliki SEMUA method
    var a Animal = &Dog{Name: "Buddy"}  // ✓
    a.Sound()
    a.Move()

    // Dog TIDAK mengimplementasikan Animal — value tidak punya method pointer
    // var b Animal = Dog{Name: "Buddy"}  // ✗ compile error:
    // Dog does not implement Animal (Move method has pointer receiver)
}
Aturan Method Set:

  Tipe T memiliki method:
    → Semua method dengan VALUE receiver (T)

  Tipe *T memiliki method:
    → Semua method dengan VALUE receiver (T)
    → Semua method dengan POINTER receiver (*T)

  Implikasi untuk interface:
    → Jika interface memiliki method dengan pointer receiver,
      hanya *T yang bisa memenuhi interface, bukan T
    → Gunakan pointer (&value) saat assign ke interface jika
      ada method dengan pointer receiver

Contoh Program Lengkap #

Program berikut membangun sistem notifikasi multi-channel yang mendemonstrasikan dependency injection via interface:

package main

import (
    "fmt"
    "strings"
    "time"
)

// ── Interface Definitions ─────────────────────────────────────

type Notifier interface {
    Send(recipient, message string) error
    Name() string
}

type NotificationStore interface {
    Save(n Notification) error
    FindByRecipient(recipient string) []Notification
}

// ── Domain Types ──────────────────────────────────────────────

type Priority int

const (
    PriorityLow Priority = iota
    PriorityNormal
    PriorityHigh
    PriorityCritical
)

func (p Priority) String() string {
    switch p {
    case PriorityLow:      return "Low"
    case PriorityNormal:   return "Normal"
    case PriorityHigh:     return "High"
    case PriorityCritical: return "Critical"
    default:               return "Unknown"
    }
}

type Notification struct {
    ID        int
    Recipient string
    Message   string
    Channel   string
    Priority  Priority
    SentAt    time.Time
    Success   bool
    Error     string
}

// ── Concrete Notifier Implementations ────────────────────────

type EmailNotifier struct {
    SMTPHost string
    From     string
    sentCount int
}

func (e *EmailNotifier) Send(recipient, message string) error {
    // Simulasi pengiriman email
    e.sentCount++
    fmt.Printf("  📧 [EMAIL] To: %s\n     %s\n", recipient, message)
    return nil
}

func (e *EmailNotifier) Name() string { return "Email" }

type SlackNotifier struct {
    WebhookURL string
    Channel    string
}

func (s *SlackNotifier) Send(recipient, message string) error {
    fmt.Printf("  💬 [SLACK] #%s @%s: %s\n", s.Channel, recipient, message)
    return nil
}

func (s *SlackNotifier) Name() string { return "Slack" }

type SMSNotifier struct {
    APIKey   string
    FromNum  string
}

func (s *SMSNotifier) Send(recipient, message string) error {
    // SMS biasanya dibatasi panjangnya
    if len(message) > 160 {
        message = message[:157] + "..."
    }
    fmt.Printf("  📱 [SMS] To: %s | %s\n", recipient, message)
    return nil
}

func (s *SMSNotifier) Name() string { return "SMS" }

// ── In-Memory Store ───────────────────────────────────────────

type InMemoryStore struct {
    notifications []Notification
    nextID        int
}

func (s *InMemoryStore) Save(n Notification) error {
    s.nextID++
    n.ID = s.nextID
    s.notifications = append(s.notifications, n)
    return nil
}

func (s *InMemoryStore) FindByRecipient(recipient string) []Notification {
    var result []Notification
    for _, n := range s.notifications {
        if n.Recipient == recipient {
            result = append(result, n)
        }
    }
    return result
}

// ── Notification Service ──────────────────────────────────────

type NotificationService struct {
    notifiers map[string]Notifier
    store     NotificationStore
}

func NewNotificationService(store NotificationStore) *NotificationService {
    return &NotificationService{
        notifiers: make(map[string]Notifier),
        store:     store,
    }
}

func (ns *NotificationService) Register(notifier Notifier) {
    ns.notifiers[notifier.Name()] = notifier
}

func (ns *NotificationService) Notify(
    recipient, message string,
    priority Priority,
    channels ...string,
) {
    // Tentukan channel berdasarkan priority jika tidak ditentukan
    if len(channels) == 0 {
        switch priority {
        case PriorityCritical:
            channels = []string{"Email", "SMS", "Slack"}
        case PriorityHigh:
            channels = []string{"Email", "Slack"}
        default:
            channels = []string{"Email"}
        }
    }

    fmt.Printf("\n[%s] Kirim ke %s (Priority: %s):\n",
        time.Now().Format("15:04:05"), recipient, priority)

    for _, ch := range channels {
        notifier, ok := ns.notifiers[ch]
        if !ok {
            fmt.Printf("  ⚠ Channel %q tidak terdaftar\n", ch)
            continue
        }

        n := Notification{
            Recipient: recipient,
            Message:   message,
            Channel:   ch,
            Priority:  priority,
            SentAt:    time.Now(),
        }

        err := notifier.Send(recipient, message)
        if err != nil {
            n.Error = err.Error()
            fmt.Printf("  ✗ Gagal kirim via %s: %v\n", ch, err)
        } else {
            n.Success = true
        }

        _ = ns.store.Save(n)
    }
}

func (ns *NotificationService) History(recipient string) {
    notifications := ns.store.FindByRecipient(recipient)
    if len(notifications) == 0 {
        fmt.Printf("\nTidak ada riwayat notifikasi untuk %s\n", recipient)
        return
    }

    fmt.Printf("\n=== Riwayat Notifikasi: %s ===\n", recipient)
    for _, n := range notifications {
        status := "✓"
        if !n.Success {
            status = "✗"
        }
        fmt.Printf("  [%s] %s via %-6s | %s\n",
            status,
            n.SentAt.Format("15:04:05"),
            n.Channel,
            truncate(n.Message, 50),
        )
    }
}

func truncate(s string, n int) string {
    if len(s) <= n {
        return s
    }
    return s[:n-3] + "..."
}

// ── Main ──────────────────────────────────────────────────────

func main() {
    // Setup — dependency injection via interface
    store := &InMemoryStore{}
    svc := NewNotificationService(store)

    // Register notifiers — semua mengimplementasikan Notifier
    svc.Register(&EmailNotifier{SMTPHost: "smtp.example.com", From: "[email protected]"})
    svc.Register(&SlackNotifier{WebhookURL: "https://hooks.slack.com/...", Channel: "alerts"})
    svc.Register(&SMSNotifier{APIKey: "twilio-key", FromNum: "+62800000000"})

    // Kirim berbagai notifikasi
    svc.Notify("[email protected]", "Selamat datang di platform kami!", PriorityNormal)

    svc.Notify("[email protected]",
        "Transaksi Rp 5.000.000 berhasil diproses",
        PriorityHigh)

    svc.Notify("[email protected]",
        "CRITICAL: Server CPU usage mencapai 98%! Segera periksa!",
        PriorityCritical)

    svc.Notify("[email protected]",
        "Laporan bulanan kamu sudah siap diunduh",
        PriorityLow,
        "Email")  // override channel

    // Lihat riwayat
    svc.History("[email protected]")
    svc.History("[email protected]")

    // Demonstrasi type assertion — cek kapabilitas tambahan
    fmt.Println("\n=== Info Notifier ===")
    for name, notifier := range svc.notifiers {
        info := fmt.Sprintf("%-10s", name)

        // Type assertion untuk cek apakah EmailNotifier
        if emailNotifier, ok := notifier.(*EmailNotifier); ok {
            info += fmt.Sprintf(" | SMTP: %s | Terkirim: %d",
                emailNotifier.SMTPHost,
                emailNotifier.sentCount)
        }

        // Type switch untuk info spesifik per tipe
        switch n := notifier.(type) {
        case *SlackNotifier:
            info += fmt.Sprintf(" | Channel: #%s", n.Channel)
        case *SMSNotifier:
            info += fmt.Sprintf(" | From: %s", n.FromNum)
        }

        fmt.Printf("  %s\n", info)
    }

    // Demonstrasi: interface kecil di sisi konsumer
    var channels []string
    for name := range svc.notifiers {
        channels = append(channels, name)
    }
    fmt.Printf("\nChannel tersedia: %s\n", strings.Join(channels, ", "))
}

Ringkasan #

  • Implicit implementation — tidak ada implements; tipe otomatis memenuhi interface jika memiliki semua method yang disyaratkan.
  • Interface = kontrak perilaku — hanya method signature, tidak ada field atau implementasi.
  • Interface value menyimpan (type, value) — variabel interface yang berisi nil pointer tidak sama dengan nil interface; selalu return nil eksplisit.
  • Interface kecil lebih kuatio.Reader dengan satu method dipakai ribuan kali; hindari interface dengan 10+ method.
  • Komposisi interface — embed interface ke interface lain untuk kontrak yang lebih kaya.
  • any / interface{} kehilangan type safety — gunakan generics sejak Go 1.18 untuk container generik yang type-safe.
  • Type assertion safe (val, ok := i.(Type)) — selalu gunakan two-value form untuk menghindari panic.
  • Type switch untuk menangani banyak kemungkinan tipe dengan elegan.
  • Definisikan interface di sisi konsumer — setiap konsumer mendefinisikan interface sekecil yang dibutuhkan, bukan satu interface besar di sisi produsen.
  • Method set: *T memiliki method value dan pointer receiver; T hanya memiliki method value receiver — gunakan pointer saat assign ke interface jika ada pointer receiver.

← Sebelumnya: Struct   Berikutnya: Eksepsi →

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