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
- Method Definitions (Value Receivers vs. Pointer Receivers)
- Interface Definition and Implicit Implementation (Duck Typing)
- The Purpose of the Empty Interface
interface{} - Type assertions and type switches
- Interface Composition (Embedded Interfaces)
io.Reader/io.Writerstandard interfaces- Building a Switchable Multi-Payment Gateway Using Interfaces
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:
// 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
// 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:
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 |
implements: you can even have types from third-party packages implement interfaces defined in external packages.
3. Method Definitions
(1) طريقة = a دالة with a receiver
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
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:
After value receiver: 11
After pointer receiver: 12
▶ Example: Selecting Between Value and Pointer Receivers
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())
}
Output:
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
// Define an interface: a set of طريقة signatures
type Stringer interface {
String() سلسلة
}
▶ Example: Implicit Implementation
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)
}
Output:
Buddy says: Woof!
Whiskers says: Meow!
(3) Interface values: dynamic type + dynamic value
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:
nil: <nil>, <nil>
type=main.Dog, value={Buddy}
5. The Empty Interface interface{} and Type Assertions
(1) Empty interface: any type
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:
type=int, value=42
type=string, value=hello
type=float64, value=3.14
type=main.Dog, value={Buddy}
▶ Example: Type assertion (comma-ok syntax)
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)
}
Output:
String: hello (len=5)
Int: 42 (double=84)
Unknown type: float64 = 3.14
(3) Type switch
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:
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
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
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")
}
Output:
[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
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
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.
}
Output:
=== strings.Reader ===
read: "hello wo"
read: "rld"
(3) The io.Reader + io.Writer combination (the standard library's chain)
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:
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:
// 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:
[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
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)
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
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.interface{}?fmt.Println(a ...interface{}); (2) storing values of different types in a map: map[string]interface{}; (3) data deserialization (parsing JSON into interface{}).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.Read method of io.Reader return EOF?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.📖 Summary
- Method = a function with a receiver; the receiver can be a value or a pointer
- A value receiver operates on a copy, while a pointer receiver modifies the original object
- An interface is a collection of method signatures; Go uses implicit implementation (duck typing)
- The empty interface
interface{}represents any type - The type assertion
v.(T)extracts the dynamic value of the interface;comma-okprevents a panic - Interface composition creates a new interface by embedding interfaces (
type A interface { B; C }) io.Readerandio.Writerare the two most fundamental interfaces in the Go standard library- Interfaces ensure that code follows the "Open-Closed Principle"—open for extension, closed for modification
📝 Exercises
-
Basic Problem (Difficulty ⭐): Define the
Shapeinterface (Area() float64), implement two structs—Circle(radius) andRectangle(width and height)—and calculate and print their areas. -
Advanced Problem (Difficulty ⭐⭐): Implement a
Cacheinterface (Get(key string) (interface{}, bool)/Set(key string, value interface{})), usingmap[string]interface{}and a memory-constrained version (maximum of 10 keys) to implement two different strategies. You must use pointer receivers. -
Challenge Problem (Difficulty ⭐⭐⭐): Implement a pluggable storage backend: Define the
Storeinterface (Save(key string, data []byte) error/Load(key string) ([]byte, error)/Delete(key string) error), implementMemoryStore(map storage) andFileStore(file storage usingos.WriteFile/os.ReadFile), and finally use aBackupServiceto synchronize data between the two storage types.