Go: طرق وواجهات لغة Go

آخر تحديث: 2026-08-26

«الطريقة» (Method) هي دالة تتلقى متلقيًّا، أما «الواجهة» (Interface) فهي مجموعة من توقيعات الطرق — وتُنفِّذ لغة Go «التجريد السلوكي» الموجه للكائنات باستخدام بناء جملة موجز، دون التعقيدات التي ينطوي عليها وراثة الفئات.

لا تحتوي لغة Go على فئات، لكنها تحتوي على طرق؛ ولا تحتوي على الكلمة الرئيسية implements، لكنها تحتوي على واجهات تستند إلى نمط «الكتابة حسب الشكل» (duck typing). في هذا الدرس، ستتقن جميع المفاهيم الأساسية للبرمجة الموجهة للكائنات في لغة Go، وستتعلم كيفية استخدام الواجهات لإنشاء نظام بوابة دفع متعددة الخيارات وقابل للتبديل.

1. ستتعلم



2. قصة حقيقية لمهندس مدفوعات في مجال التجارة الإلكترونية

(1) المشكلة: كلمة «Switch» مبرمجة بشكل ثابت؛ وإضافة بوابة دفع جديدة تتطلب تعديل الكود الأساسي.

تشارلي هو مهندس «الخلفية» في منصة للتجارة الإلكترونية. وهو مسؤول عن صيانة وحدة الدفع:

"نحن ندعم Stripe، ونريد الآن إضافة PayPal. لكن كود الدفع مليء بـ if gateway == "stripe"، لذا فإن إضافة PayPal ستتطلب إعادة كتابة الملف بأكمله."

فتح الملف البرمجي الذي كتبه سلفه:

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)
    }
}

في كل مرة تُضاف فيها بوابة دفع، يتعين تعديل الدالة charge — وهو ما يخالف مبدأ الانفتاح والإغلاق (مفتوحة للتوسعة، مغلقة للتعديل).

(2) حل Go: تنفيذ الواجهة الضمنية

GO
// payment.go
package main

import "fmt"

// Define the Payment Interface
type PaymentGateway interface {
    Charge(amount float64) error
    Refund(transactionID string) error
}

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

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

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

// PayPal Implementation
type PayPal struct {
    email string
}

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

func (p PayPal) Refund(txID string) error {
    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) error {
    return gw.Charge(amount)
}

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

    // The same processPayment function can accept different concrete types.
    processPayment(stripe, 99.99)
    processPayment(paypal, 49.99)
}

الناتج:

TEXT 📖 للعرض فقط
Stripe: charged $99.99
PayPal: charged $49.99

(3) الفوائد: مبدأ «الفتح والإغلاق»

الطريقة إضافة بوابة جديدة تعديل كود النواة المخاطر
التبديل المبرمج ثابتًا تعديل الدالة charge ✅ مطلوب 🔴 عالي
تجريد الواجهة إنشاء بنية جديدة لتنفيذ الواجهة ❌ غير مطلوب 🟢 منخفض
💡 نصيحة: يتم تنفيذ الواجهات في لغة Go بشكل ضمني — فما دام البنية (struct) تحتوي على جميع الطرق المُعرَّفة في واجهة ما، فإنها تُنفِّذ تلك الواجهة «تلقائيًّا». ويُعد هذا أكثر مرونة من نهج implements في لغة Java: حيث يمكنك حتى جعل أنواع من حزم تابعة لجهات خارجية تُنفِّذ واجهات مُعرَّفة في حزم خارجية.



3. تعريفات الطرق

(1) الطريقة = دالة ذات متلقي

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) متلقي القيم مقابل متلقي المؤشرات

GO
package main

import "fmt"

type Counter struct {
    Value int
}

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

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

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

    // Value receiver must use the return القيمة
    c = c.IncrementValue()
    fmt.Printf("After القيمة receiver: %d\n", c.Value)

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

الناتج:

TEXT 📖 للعرض فقط
After value receiver: 11
After pointer receiver: 12

▶ مثال: الاختيار بين متلقي القيمة ومتلقي المؤشر

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())
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Alice Smith (28)
Sum: 499500

(4) دليل لاختيار ما بين مستقبلات القيمة ومستقبلات المؤشر

السيناريو نوع جهاز الاستقبال السبب
لا تُحدث هذه الطريقة أي تغيير في المتلقي يُسمح باستخدام كل من القيم والمؤشرات تُعد متلقيات القيم أكثر أمانًا (لا توجد آثار جانبية)
يجب أن تقوم الطريقة بتعديل المتلقي مؤشر يقوم متلقي القيمة بتعديل نسخة
الهياكل الكبيرة (> 100 بايت) المؤشر تجنب نسخ الكائنات الكبيرة
المتلقي هو خريطة/شريحة/دالة القيم (وهي أنواع مرجعية) مرجع بالفعل
النوع هو نوع أساسي القيمة (لا حاجة لمؤشر) حجم صغير، عبء نسخ منخفض


4. الواجهات: التنفيذ الضمني (الكتابة حسب النوع)

(1) تعريف الواجهة

GO
// Define an interface: a set of method signatures
type Stringer interface {
    String() string
}

▶ مثال: التنفيذ الضمني

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)
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Buddy says: Woof!
Whiskers says: Meow!

(3) قيم الواجهة: النوع الديناميكي + القيمة الديناميكية

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)
}

الناتج:

TEXT 📖 للعرض فقط
nil: <nil>, <nil>
type=main.Dog, القيمة={Buddy}


5. الواجهة الفارغة interface{} وتأكيدات الأنواع

(1) واجهة فارغة: أي نوع

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"})
}

الناتج:

TEXT 📖 للعرض فقط
type=int, value=42
type=string, value=hello
type=float64, value=3.14
type=main.Dog, value={Buddy}

▶ مثال: تأكيد النوع (صيغة «الفاصلة مسموح بها»)

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)
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
String: hello (len=5)
Int: 42 (double=84)
Unknown type: float64 = 3.14

(3) مفتاح التبديل

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})
}

الناتج:

TEXT 📖 للعرض فقط
string: "hello" (len=5)
int: 42
float64: 3.14
bool: true
unknown: []int

(4) تأكيدات الأنواع مقابل مفاتيح الأنواع

السيناريو التوصية
التحقق مما إذا كان من نوع معين تأكيد النوع v.(T)
تحديد أنواع متعددة مفتاح التبديل v.(type)
ما عليك سوى التحقق (لا حاجة إلى إدخال قيمة) _, ok := v.(T)


6. تكوين الواجهة

(1) إنشاء واجهة جديدة عن طريق دمج واجهة موجودة

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")
}

▶ مثال: التطبيق العملي لتركيب الواجهات

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")
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
[LOG] System startup
[NOTIFY] User Alice logged in

(3) مرجع سريع لطرق تكوين الواجهة

التركيبة الصيغة الوصف
تضمين واجهة واحدة type A interface { B } تحتوي A على جميع أساليب B
تضمين واجهات متعددة type A interface { B; C } A تحتوي على جميع طرق B و C
التضمين + طرق جديدة type A interface { B; C; Do() } تحتوي A على الطرق B و C و Do


7. الواجهات القياسية io.Reader و io.Writer

(1) أهم واجهتين في المكتبة القياسية

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

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

▶ مثال: تطبيق «Reader» بطرق مختلفة

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.
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
=== strings.Reader ===
read: "hello wo"
read: "rld"

(3) التركيبة io.Reader + io.Writer (سلسلة المكتبة القياسية)

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())
}

الناتج:

TEXT 📖 للعرض فقط
copied 19 bytes: "hello Go interfaces"

(4) قائمة الأنواع القياسية التي تُنفِّذ io.Reader

النوع الحزمة تنفذ
strings.Reader سلاسل القارئ
bytes.Reader بايت القارئ
os.File نظام التشغيل قارئ + كاتب
bytes.Buffer بايت قارئ + كاتب
net.Conn شبكة قارئ + كاتب
gzip.Reader ضغط/gzip القارئ


8. مثال كامل: تجريد بوابة الدفع المتعددة

دعونا نربط جميع المفاهيم الأساسية التي تناولناها في هذا الدرس معًا من أجل بناء نظام دفع متكامل:

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)
}

النتيجة المتوقعة:

TEXT 📖 للعرض فقط
[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)
🔥 خطأ شائع: الحقل gateway في PaymentService هو نوع واجهة، وليس نوعًا ملموسًا. يمكن لمتغير الواجهة أن يحتوي على أي قيمة تنفذ تلك الواجهة — وهذا هو الأساس الذي يستند إليه تنفيذ نمط الاستراتيجية في لغة Go.



❓ أسئلة شائعة

س كيف أختار بين أنواع المستقبلات التي تعمل بالقيمة وتلك التي تعمل بالمؤشر؟
ج ثلاث قواعد: (1) إذا كان من الضروري تعديل المستقبل → استخدم المؤشر؛ (2) الهياكل الكبيرة (> 100 بايت) → استخدم المؤشر؛ (3) في جميع الحالات الأخرى، يُفضل استخدام المستقبلات التي تعمل بالقيمة. إذا كان النوع يستخدم مستقبلاً يعمل بالمؤشر، يُنصح بأن تتبع جميع الطرق نفس القاعدة.
س كيف يمكنني فهم مفهوم «التنفيذ الضمني للواجهة»؟
ج طالما أن البنية (struct) تحتوي على جميع توقيعات الطرق المُعرَّفة في واجهة ما، فإنها تُنفِّذ تلك الواجهة تلقائيًّا — دون الحاجة إلى الكلمة الرئيسية implements. وهذا يعني: (1) أن الأنواع الواردة من حزم الجهات الخارجية يمكنها أيضًا تنفيذ الواجهات التي تُعرِّفها؛ (2) أن النوع الواحد يمكنه تنفيذ عدة واجهات غير مرتبطة ببعضها على الإطلاق.
س ما الغرض من الواجهة الفارغة interface{}؟
ج إنها تقبل قيمًا من أي نوع. حالات الاستخدام الشائعة: (1) fmt.Println(a ...interface{})؛ (2) تخزين قيم من أنواع مختلفة في خريطة: map[string]interface{}؛ (3) إزالة تسلسل البيانات (تحليل JSON إلى interface{}).
س ماذا عليّ أن أفعل إذا فشل تأكيد النوع؟
ج استخدم صيغة «comma-ok»: v, ok := x.(T). لن يؤدي ذلك إلى حدوث حالة ذعر إذا كانت قيمة ok خاطئة. إذا لم تستخدم صيغة «comma-ok»، فإن فشل التأكيد سيؤدي إلى حدوث حالة ذعر: ستحدث حالة ذعر في v := x.(string) إذا لم تكن x سلسلة نصية.
س ما الفرق بين تكوين الواجهة والهياكل المتداخلة؟
ج (1) تكوين الواجهة → يجمع بين توقيعات الطرق (إعادة استخدام السلوك)؛ (2) الهياكل المتداخلة → تجمع بين الحقول والطرق (إعادة استخدام البيانات). يندرج تكوين الواجهة تحت مصطلح «التجريد السلوكي»، بينما تندرج الهياكل المتداخلة تحت مصطلح «تجميع البيانات».
س هل قيم الواجهة من أنواع القيم أم أنواع المراجع؟
ج قيم الواجهة هي أنواع مراجع. يخزن متغير الواجهة داخليًّا زوجًا (النوع، القيمة) — وعند تعيين قيمة له، يتم نسخ هذا الزوج، وليس البيانات الأساسية. ولذلك، فإن تمرير قيم الواجهة كمعلمات عملية خفيفة جدًّا (مؤشران فقط).
س متى تُرجع طريقة Read الخاصة بـ io.Reader القيمة EOF؟
ج عندما يتم قراءة جميع البيانات، تُرجع Read (0، io.EOF). لاحظ أن EOF يشير إلى نهاية عادية، وليس إلى خطأ — لذا لا يمكنك استخدام err != nil للتحقق من اكتمال القراءة؛ بل يجب عليك استخدام err == io.EOF.
س هل يمكن لنوع ما أن ينفذ واجهات متعددة في آن واحد؟
ج نعم. طالما أن النوع يحتوي على جميع الطرق الموجودة في واجهات متعددة، فإنه ينفذها جميعًا ضمناً. وتعد هذه أيضًا إحدى المزايا الأساسية لتصميم الواجهات في لغة Go — وهي المرونة في التركيب دون الحاجة إلى تسلسل هرمي للوراثة.

📖 ملخص


📝 تمارين

  1. المسألة الأساسية (صعوبة ⭐): عرّف واجهة Shape (Area() float64)، وقم بتنفيذ هيكلين — Circle (نصف القطر) وRectangle (العرض والارتفاع) — واحسب مساحتيهما واطبعها.

  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ واجهة Cache (Get(key string) (interface{}, bool) / Set(key string, value interface{}))، باستخدام map[string]interface{} ونسخة محدودة الذاكرة (بحد أقصى 10 مفاتيح) لتنفيذ استراتيجيتين مختلفتين. يجب عليك استخدام مستقبلات المؤشرات.

  3. مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ خلفية تخزين قابلة للتوصيل: حدد واجهة Store (Save(key string, data []byte) error / Load(key string) ([]byte, error) / Delete(key string) error)، وقم بتنفيذ MemoryStore (تخزين الخرائط) وFileStore (تخزين الملفات باستخدام os.WriteFile / os.ReadFile)، وأخيرًا استخدم BackupService لمزامنة البيانات بين نوعي التخزين.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%