Go: Go Methods and Interfaces

Last updated: 2026-08-26

A طريقة is a دالة that takes a receiver, and an interface is a collection of طريقة signatures—Go implements كائن-oriented "behavioral abstraction" with concise syntax, without the complexity of فئة inheritance.

Go doesn't have classes, but it does have methods; it doesn't have an implements keyword, but it does have interfaces based on duck typing. In this lesson, you'll master all the core concepts of كائن-oriented programming in Go and learn how to use interfaces to build a switchable multi-payment gateway system.

1. You will learn



2. A True Story of an E-commerce Payment Engineer

(1) Pain point: The switch statement is hard-coded; adding a new payment gateway requires modifying the core code.

Charlie is a واجهة خلفية engineer at an e-commerce platform. He maintains a payment module:

"We support Stripe, and now we want to add PayPal. But the payment code is full of if gateway == "stripe", so adding PayPal would mean rewriting the entire file."

He opened the code written by his predecessor:

GO
// Bad code: Hard-coded payment logic
func charge(amount float64, gateway string) error {
    switch gateway {
    case "stripe":
        // Stripe HTTP API calls...
        return stripeCharge(amount)
    case "paypal":
        // Adding PayPal means I have to add another case here.
        return nil
    default:
        return fmt.Errorf("unknown gateway: %s", gateway)
    }
}

Every time a payment gateway is added, the charge function has to be modified—which violates the Open-Closed Principle (open for extension, closed for modification).

(2) The Go Solution: Implicit Interface Implementation

GO
// payment.go
package main

import "fmt"

// Define the Payment Interface
type PaymentGateway interface {
    Charge(amount float64) خطأ
    Refund(transactionID سلسلة) خطأ
}

// Stripe Implementation (No need to write "implements"!)
type Stripe struct {
    apiKey سلسلة
}

func (s Stripe) Charge(amount float64) خطأ {
    fmt.Printf("Stripe: charged $%.2f\n", amount)
    return nil
}

func (s Stripe) Refund(txID سلسلة) خطأ {
    fmt.Printf("Stripe: refunded %s\n", txID)
    return nil
}

// PayPal Implementation
type PayPal struct {
    email سلسلة
}

func (p PayPal) Charge(amount float64) خطأ {
    fmt.Printf("PayPal: charged $%.2f\n", amount)
    return nil
}

func (p PayPal) Refund(txID سلسلة) خطأ {
    fmt.Printf("PayPal: refunded %s\n", txID)
    return nil
}

// Consumer Code: Depends only on the interface, not on the concrete impl
func processPayment(gw PaymentGateway, amount float64) خطأ {
    return gw.Charge(amount)
}

func main() {
    stripe := Stripe{apiKey: "sk_test_xxx"}
    paypal := PayPal{email: "merchant@example.com"}

    // The same processPayment دالة can accept different concrete types.
    processPayment(stripe, 99.99)
    processPayment(paypal, 49.99)
}

Output:

TEXT 📖 Display only
Stripe: charged $99.99
PayPal: charged $49.99

(3) Benefits: The Open-Closed Principle

Method Add a new gateway Modify core code Risk
switch hard-coded Modify charge دالة ✅ Required 🔴 High
Interface Abstraction Create a new struct to implement the interface ❌ Not required 🟢 Low
💡 Tip: Interfaces in Go are implicitly implemented—as long as a struct has all the methods defined in an interface, it "automatically" implements that interface. This is more flexible than Java's implements: you can even have types from third-party packages implement interfaces defined in external packages.



3. Method Definitions

(1) طريقة = a دالة with a receiver

GO
package main

import "fmt"

type Rectangle struct {
    Width  float64
    Height float64
}

// Method: The receiver (r Rectangle) is placed between the func keyword and the function name
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    fmt.Printf("Area: %.2f\n", rect.Area())  // Area: 50.00
}

(2) Value Receivers vs. Pointer Receivers

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Value receiver: operates on a copy; does not affect the original كائن
func (c Counter) IncrementValue() Counter {
    c.Value++
    return c
}

// Pointer receiver: directly modifies the original كائن
func (c *Counter) IncrementPointer() {
    c.Value++
}

func main() {
    c := Counter{Value: 10}

    // Value receiver must use the return value
    c = c.IncrementValue()
    fmt.Printf("After value receiver: %d\n", c.Value)

    // Pointer receiver modifies directly
    c.IncrementPointer()
    fmt.Printf("After pointer receiver: %d\n", c.Value)
}

Output:

TEXT 📖 Display only
After value receiver: 11
After pointer receiver: 12

▶ Example: Selecting Between Value and Pointer Receivers

GO
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// Value receiver: suitable for small objects and read-only operations
func (u User) Info() string {
    return fmt.Sprintf("%s (%d)", u.Name, u.Age)
}

// Pointer receiver: suitable for large objects and modification operations
func (u *User) SetName(name string) {
    u.Name = name
}

type LargeData struct {
    data [1000]int
}

// Large structures must use pointer receivers (to avoid copying 1000 ints)
func (l *LargeData) Process() int {
    sum := 0
    for _, v := range l.data {
        sum += v
    }
    return sum
}

func main() {
    u := User{Name: "Alice", Age: 28}
    u.SetName("Alice Smith")
    fmt.Println(u.Info())

    ld := LargeData{}
    for i := 0; i < 1000; i++ {
        ld.data[i] = i
    }
    fmt.Printf("Sum: %d\n", ld.Process())
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Alice Smith (28)
Sum: 499500

(4) Guide to Choosing Between Value and Pointer Receivers

Scenario Receiver Type Reason
The method does not modify the receiver Both values and pointers are acceptable Value receivers are safer (no side effects)
The method needs to modify the receiver Pointer A value receiver modifies a copy
Large structures (> 100 bytes) Pointer Avoid copying large objects
The receiver is a map/slice/func Values (which are reference types) Already a reference
Type is a primitive type Value (no pointer needed) Small, low copying overhead


4. Interfaces: Implicit Implementation (Duck Typing)

(1) Interface Definition

GO
// Define an interface: a set of طريقة signatures
type Stringer interface {
    String() سلسلة
}

▶ Example: Implicit Implementation

GO
package main

import "fmt"

// 1. Define an interface
type Speaker interface {
    Speak() string
}

// 2. Define two structs, both of which implement the Speak method
type Dog struct{ Name string }

func (d Dog) Speak() string {
    return fmt.Sprintf("%s says: Woof!", d.Name)
}

type Cat struct{ Name string }

func (c Cat) Speak() string {
    return fmt.Sprintf("%s says: Meow!", c.Name)
}

// 3. Consumer function: accepts an interface
func greet(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{Name: "Buddy"}
    cat := Cat{Name: "Whiskers"}

    // Both Dog and Cat implicitly implement Speaker; the implements keyword is not required.
    greet(dog)
    greet(cat)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Buddy says: Woof!
Whiskers says: Meow!

(3) Interface values: dynamic type + dynamic value

GO
package main

import "fmt"

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string {
    return fmt.Sprintf("%s says: Woof!", d.Name)
}

func main() {
    var s Speaker          // interface variable, defaults to nil
    fmt.Printf("nil: %T, %v\n", s, s)

    s = Dog{Name: "Buddy"} // interface stores dynamic type and dynamic value
    fmt.Printf("type=%T, value=%v\n", s, s)
}

Output:

TEXT 📖 Display only
nil: <nil>, <nil>
type=main.Dog, value={Buddy}


5. The Empty Interface interface{} and Type Assertions

(1) Empty interface: any type

GO
package main

import "fmt"

type Dog struct {
    Name string
}

// An empty interface can store any type
func describe(v interface{}) {
    fmt.Printf("type=%T, value=%v\n", v, v)
}

func main() {
    describe(42)
    describe("hello")
    describe(3.14)
    describe(Dog{Name: "Buddy"})
}

Output:

TEXT 📖 Display only
type=int, value=42
type=string, value=hello
type=float64, value=3.14
type=main.Dog, value={Buddy}

▶ Example: Type assertion (comma-ok syntax)

GO
package main

import "fmt"

func printValue(v interface{}) {
    // Type assertion: extract underlying value
    if s, ok := v.(string); ok {
        fmt.Printf("String: %s (len=%d)\n", s, len(s))
        return
    }
    if n, ok := v.(int); ok {
        fmt.Printf("Int: %d (double=%d)\n", n, n*2)
        return
    }
    fmt.Printf("Unknown type: %T = %v\n", v, v)
}

func main() {
    printValue("hello")
    printValue(42)
    printValue(3.14)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
String: hello (len=5)
Int: 42 (double=84)
Unknown type: float64 = 3.14

(3) Type switch

GO
package main

import "fmt"

func inspect(v interface{}) {
    switch val := v.(type) {
    case string:
        fmt.Printf("string: %q (len=%d)\n", val, len(val))
    case int:
        fmt.Printf("int: %d\n", val)
    case float64:
        fmt.Printf("float64: %.2f\n", val)
    case bool:
        fmt.Printf("bool: %v\n", val)
    default:
        fmt.Printf("unknown: %T\n", val)
    }
}

func main() {
    inspect("hello")
    inspect(42)
    inspect(3.14)
    inspect(true)
    inspect([]int{1, 2, 3})
}

Output:

TEXT 📖 Display only
string: "hello" (len=5)
int: 42
float64: 3.14
bool: true
unknown: []int

(4) Type Assertions vs. Type Switches

Scenario Recommendation
Check if it is a certain type Type assertion v.(T)
Check multiple types Type switch v.(type)
Just check (no value needed) _, ok := v.(T)


6. Interface Composition

(1) Create a new interface by embedding an existing interface

GO
package main

import "fmt"

type Reader interface {
    Read(p []byte) (n int, err error)
}

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

// Combine Reader and Writer to form a new interface
type ReadWriter interface {
    Reader
    Writer
}

// Implementation
type File struct{}

func (f File) Read(p []byte) (n int, err error) {
    return len(p), nil
}

func (f File) Write(p []byte) (n int, err error) {
    return len(p), nil
}

func main() {
    var rw ReadWriter = File{}
    buf := make([]byte, 10)
    rw.Read(buf)
    rw.Write(buf)
    fmt.Println("ReadWriter composite interface works as expected")
}

▶ Example: Practical Application of Interface Composition

GO
package main

import "fmt"

type Logger interface {
    Log(message string)
}

type Notifier interface {
    Notify(message string)
}

// Composition
type LoggerNotifier interface {
    Logger
    Notifier
}

type ConsoleService struct{}

func (c ConsoleService) Log(message string) {
    fmt.Printf("[LOG] %s\n", message)
}

func (c ConsoleService) Notify(message string) {
    fmt.Printf("[NOTIFY] %s\n", message)
}

func main() {
    var svc LoggerNotifier = ConsoleService{}
    svc.Log("System startup")
    svc.Notify("User Alice logged in")
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
[LOG] System startup
[NOTIFY] User Alice logged in

(3) Quick Reference for Interface Composition Methods

Combination Syntax Description
Embedding a Single Interface type A interface { B } A contains all of B's methods
Embedding Multiple Interfaces type A interface { B; C } A contains all the methods of B and C
Embedding + New Methods type A interface { B; C; Do() } A contains methods B, C, and Do


7. The io.Reader / io.Writer Standard Interfaces

(1) The two most essential interfaces in the standard library

GO
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

▶ Example: Implementing Reader in Various Ways

GO
package main

import (
    "fmt"
    "io"
    "strings"
)

func printReader(r io.Reader) {
    buf := make([]byte, 8)
    for {
        n, err := r.Read(buf)
        if err == io.EOF {
            break
        }
        fmt.Printf("read: %q\n", buf[:n])
    }
}

func main() {
    // strings.Reader implements io.Reader
    fmt.Println("=== strings.Reader ===")
    printReader(strings.NewReader("hello world"))

    // You can also use bytes.Reader, os.File, etc.
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
=== strings.Reader ===
read: "hello wo"
read: "rld"

(3) The io.Reader + io.Writer combination (the standard library's chain)

GO
package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    // Implementing a copy using io.Reader and io.Writer
    reader := strings.NewReader("hello Go interfaces")
    writer := &strings.Builder{}

    // io.Copy accepts any Reader and Writer
    n, _ := io.Copy(writer, reader)
    fmt.Printf("copied %d bytes: %q\n", n, writer.String())
}

Output:

TEXT 📖 Display only
copied 19 bytes: "hello Go interfaces"

(4) List of Standard Types That Implement io.Reader

Type Package Implements
strings.Reader strings Reader
bytes.Reader bytes Reader
os.File os Reader + Writer
bytes.Buffer bytes Reader + Writer
net.Conn net Reader + Writer
gzip.Reader compress/gzip Reader


8. Complete Example: Multi-Payment Gateway Abstraction

Let's tie together all the key concepts from this lesson to build a complete payment system:

GO
// payment_system.go
package main

import (
    "fmt"
    "time"
)

// ---------- Interface Definition ----------

type PaymentGateway interface {
    Charge(amount float64) (string, error)     // Returns transaction ID
    Refund(transactionID string) error
    Name() string
}

// Logging Interface (Composition Example)
type TransactionLogger interface {
    Log(transactionID, gateway string, amount float64, success bool)
}

// ---------- Stripe Implementation ----------

type Stripe struct {
    apiKey string
}

func (s Stripe) Charge(amount float64) (string, error) {
    txID := fmt.Sprintf("STRIPE-%s", s.txID())
    fmt.Printf("[Stripe] charging $%.2f -> %s\n", amount, txID)
    return txID, nil
}

func (s Stripe) Refund(txID string) error {
    fmt.Printf("[Stripe] refunding %s\n", txID)
    return nil
}

func (s Stripe) Name() string {
    return "Stripe"
}

func (Stripe) txID() string {
    return fmt.Sprintf("%d", time.Now().UnixNano())
}

// ---------- PayPal Implementation ----------

type PayPal struct {
    email string
}

func (p PayPal) Charge(amount float64) (string, error) {
    txID := fmt.Sprintf("PP-%s", p.txID())
    fmt.Printf("[PayPal] charging $%.2f -> %s\n", amount, txID)
    return txID, nil
}

func (p PayPal) Refund(txID string) error {
    fmt.Printf("[PayPal] refunding %s\n", txID)
    return nil
}

func (p PayPal) Name() string {
    return "PayPal"
}

func (PayPal) txID() string {
    return fmt.Sprintf("%d", time.Now().UnixNano())
}

// ---------- Alipay Implementation ----------

type Alipay struct {
    appID string
}

func (a Alipay) Charge(amount float64) (string, error) {
    txID := fmt.Sprintf("ALI-%s", a.txID())
    fmt.Printf("[Alipay] charging $%.2f -> %s\n", amount, txID)
    return txID, nil
}

func (a Alipay) Refund(txID string) error {
    fmt.Printf("[Alipay] refunding %s\n", txID)
    return nil
}

func (a Alipay) Name() string {
    return "Alipay"
}

func (Alipay) txID() string {
    return fmt.Sprintf("%d", time.Now().UnixNano())
}

// ---------- Log Implementation (Empty Interface + Type Assertion Example) ----------

type ConsoleLogger struct{}

func (c ConsoleLogger) Log(transactionID, gateway string, amount float64, success bool) {
    status := "SUCCESS"
    if !success {
        status = "FAILED"
    }
    fmt.Printf("[%s] %s | %s | $%.2f | %s\n",
        status, transactionID, gateway, amount, time.Now().Format(time.RFC3339))
}

// ---------- Payment Service ----------

type PaymentService struct {
    gateway PaymentGateway
    logger  TransactionLogger
}

func NewPaymentService(gw PaymentGateway, logger TransactionLogger) *PaymentService {
    return &PaymentService{gateway: gw, logger: logger}
}

func (s *PaymentService) Charge(amount float64) error {
    txID, err := s.gateway.Charge(amount)
    if err != nil {
        s.logger.Log("", s.gateway.Name(), amount, false)
        return err
    }
    s.logger.Log(txID, s.gateway.Name(), amount, true)
    return nil
}

func (s *PaymentService) SwitchGateway(gw PaymentGateway) {
    fmt.Printf("\nSwitch payment gateway: %s -> %s\n", s.gateway.Name(), gw.Name())
    s.gateway = gw
}

// ---------- main ----------

func main() {
    logger := ConsoleLogger{}
    stripe := Stripe{apiKey: "sk_test_xxx"}
    paypal := PayPal{email: "merchant@example.com"}
    alipay := Alipay{appID: "2025xxxx"}

    // Start with Stripe
    service := NewPaymentService(stripe, logger)
    service.Charge(99.99)
    service.Charge(49.99)

    // Switch to PayPal at runtime (flexibility provided by the interface)
    service.SwitchGateway(paypal)
    service.Charge(199.99)

    // Switch to Alipay
    service.SwitchGateway(alipay)
    service.Charge(299.99)
}

Expected Output:

TEXT 📖 Display only
[Stripe] charging $99.99 -> STRIPE-1741500000000
[SUCCESS] STRIPE-1741500000000 | Stripe | $99.99 | 2026-07-08T10:00:00Z
[Stripe] charging $49.99 -> STRIPE-1741500000001
[SUCCESS] STRIPE-1741500000001 | Stripe | $49.99 | 2026-07-08T10:00:00Z

Switch payment gateway: Stripe -> PayPal
[PayPal] charging $199.99 -> PP-1741500000002
[SUCCESS] PP-1741500000002 | PayPal | $199.99 | 2026-07-08T10:00:00Z

Switch payment gateway: PayPal -> Alipay
[Alipay] charging $299.99 -> ALI-1741500000003
[SUCCESS] ALI-1741500000003 | Alipay | $299.99 | 2026-07-08T10:00:00Z
100%
classDiagram
    class PaymentGateway {
        <<interface>>
        +Charge(amount float64) (string, error)
        +Refund(transactionID string) error
        +Name() string
    }
    class Stripe {
        -apiKey string
        +Charge(amount float64) (string, error)
        +Refund(transactionID string) error
        +Name() string
    }
    class PayPal {
        -email string
        +Charge(amount float64) (string, error)
        +Refund(transactionID string) error
        +Name() string
    }
    class Alipay {
        -appID string
        +Charge(amount float64) (string, error)
        +Refund(transactionID string) error
        +Name() string
    }
    class PaymentService {
        -gateway PaymentGateway
        -logger TransactionLogger
        +Charge(amount float64) error
        +SwitchGateway(gw PaymentGateway)
    }
    PaymentGateway <|.. Stripe : implicit impl
    PaymentGateway <|.. PayPal : implicit impl
    PaymentGateway <|.. Alipay : implicit impl
    PaymentService o--> PaymentGateway : depends on interface (Strategy Pattern)
🔥 Common Mistake: The gateway field in PaymentService is an interface type, not a concrete type. An interface variable can hold any value that implements that interface—this is the foundation of Go's implementation of the Strategy Pattern.


❓ FAQ

Q How do I choose between value and pointer receiver types?
A Three rules: (1) If the receiver needs to be modified → pointer; (2) Large structures (> 100 bytes) → pointer; (3) In all other cases, prefer value receivers. If a type uses a pointer receiver, it is recommended that all methods follow the same convention.
Q How should I understand implicit interface implementation?
A As long as a struct has all the method signatures defined in an interface, it automatically implements that interface—without needing the implements keyword. This means: (1) Types from third-party packages can also implement the interfaces you define; (2) A single type can implement multiple completely unrelated interfaces.
Q What is the purpose of the empty interface interface{}?
A It accepts values of any type. Common use cases: (1) fmt.Println(a ...interface{}); (2) storing values of different types in a map: map[string]interface{}; (3) data deserialization (parsing JSON into interface{}).
Q What should I do if a type assertion fails?
A Use the comma-ok syntax: v, ok := x.(T). It won't panic if ok is false. If you don't use the comma-ok syntax, a failed assertion will cause a panic: v := x.(string) will panic if x is not a string.
Q What is the difference between interface composition and nested structures?
A (1) Interface composition → Combines method signatures (behavior reuse); (2) Nested structs → Combines fields and methods (data reuse). Interface composition falls under "behavioral abstraction," while nested structs fall under "data aggregation."
Q Are interface values value types or reference types?
A Interface values are reference types. An interface variable internally stores a (type, value) pair—when assigned a value, it is this pair that is copied, not the underlying data. Therefore, passing interface values as parameters is very lightweight (only 2 pointers).
Q When does the Read method of io.Reader return EOF?
A When all data has been read, Read returns (0, io.EOF). Note that EOF indicates a normal end, not an error—so you cannot use err != nil to check if reading is complete; you must use err == io.EOF.
Q Can a type implement multiple interfaces at the same time?
A Yes. As long as a type has all the methods of multiple interfaces, it implicitly implements all of them. This is also a core advantage of Go's interface design—flexible composition without an inheritance hierarchy.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Define the Shape interface (Area() float64), implement two structs—Circle (radius) and Rectangle (width and height)—and calculate and print their areas.

  2. Advanced Problem (Difficulty ⭐⭐): Implement a Cache interface (Get(key string) (interface{}, bool) / Set(key string, value interface{})), using map[string]interface{} and a memory-constrained version (maximum of 10 keys) to implement two different strategies. You must use pointer receivers.

  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement a pluggable storage backend: Define the Store interface (Save(key string, data []byte) error / Load(key string) ([]byte, error) / Delete(key string) error), implement MemoryStore (map storage) and FileStore (file storage using os.WriteFile / os.ReadFile), and finally use a BackupService to synchronize data between the two storage types.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏