Go: طرق وواجهات لغة Go
آخر تحديث: 2026-08-26
«الطريقة» (Method) هي دالة تتلقى متلقيًّا، أما «الواجهة» (Interface) فهي مجموعة من توقيعات الطرق — وتُنفِّذ لغة Go «التجريد السلوكي» الموجه للكائنات باستخدام بناء جملة موجز، دون التعقيدات التي ينطوي عليها وراثة الفئات.
لا تحتوي لغة Go على فئات، لكنها تحتوي على طرق؛ ولا تحتوي على الكلمة الرئيسية implements، لكنها تحتوي على واجهات تستند إلى نمط «الكتابة حسب الشكل» (duck typing). في هذا الدرس، ستتقن جميع المفاهيم الأساسية للبرمجة الموجهة للكائنات في لغة Go، وستتعلم كيفية استخدام الواجهات لإنشاء نظام بوابة دفع متعددة الخيارات وقابل للتبديل.
1. ستتعلم
- تعريفات الطرق (المستقبلات ذات القيم مقابل المستقبلات ذات المؤشرات)
- تعريف الواجهة والتنفيذ الضمني (الكتابة حسب النوع)
- الغرض من الواجهة الفارغة
interface{} - تأكيدات الأنواع ومفاتيح التبديل بين الأنواع
- تكوين الواجهة (الواجهات المدمجة)
- واجهات قياسية
io.Reader/io.Writer - إنشاء بوابة دفع متعددة قابلة للتبديل باستخدام الواجهات
2. قصة حقيقية لمهندس مدفوعات في مجال التجارة الإلكترونية
(1) المشكلة: كلمة «Switch» مبرمجة بشكل ثابت؛ وإضافة بوابة دفع جديدة تتطلب تعديل الكود الأساسي.
تشارلي هو مهندس «الخلفية» في منصة للتجارة الإلكترونية. وهو مسؤول عن صيانة وحدة الدفع:
"نحن ندعم Stripe، ونريد الآن إضافة PayPal. لكن كود الدفع مليء بـ
if gateway == "stripe"، لذا فإن إضافة PayPal ستتطلب إعادة كتابة الملف بأكمله."
فتح الملف البرمجي الذي كتبه سلفه:
// 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: تنفيذ الواجهة الضمنية
// 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)
}
الناتج:
Stripe: charged $99.99
PayPal: charged $49.99
(3) الفوائد: مبدأ «الفتح والإغلاق»
| الطريقة | إضافة بوابة جديدة | تعديل كود النواة | المخاطر |
|---|---|---|---|
| التبديل المبرمج ثابتًا | تعديل الدالة charge |
✅ مطلوب | 🔴 عالي |
| تجريد الواجهة | إنشاء بنية جديدة لتنفيذ الواجهة | ❌ غير مطلوب | 🟢 منخفض |
implements في لغة Java: حيث يمكنك حتى جعل أنواع من حزم تابعة لجهات خارجية تُنفِّذ واجهات مُعرَّفة في حزم خارجية.
3. تعريفات الطرق
(1) الطريقة = دالة ذات متلقي
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) متلقي القيم مقابل متلقي المؤشرات
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)
}
الناتج:
After value receiver: 11
After pointer receiver: 12
▶ مثال: الاختيار بين متلقي القيمة ومتلقي المؤشر
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())
}
الناتج:
Alice Smith (28)
Sum: 499500
(4) دليل لاختيار ما بين مستقبلات القيمة ومستقبلات المؤشر
| السيناريو | نوع جهاز الاستقبال | السبب |
|---|---|---|
| لا تُحدث هذه الطريقة أي تغيير في المتلقي | يُسمح باستخدام كل من القيم والمؤشرات | تُعد متلقيات القيم أكثر أمانًا (لا توجد آثار جانبية) |
| يجب أن تقوم الطريقة بتعديل المتلقي | مؤشر | يقوم متلقي القيمة بتعديل نسخة |
| الهياكل الكبيرة (> 100 بايت) | المؤشر | تجنب نسخ الكائنات الكبيرة |
| المتلقي هو خريطة/شريحة/دالة | القيم (وهي أنواع مرجعية) | مرجع بالفعل |
| النوع هو نوع أساسي | القيمة (لا حاجة لمؤشر) | حجم صغير، عبء نسخ منخفض |
4. الواجهات: التنفيذ الضمني (الكتابة حسب النوع)
(1) تعريف الواجهة
// Define an interface: a set of method signatures
type Stringer interface {
String() string
}
▶ مثال: التنفيذ الضمني
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)
}
الناتج:
Buddy says: Woof!
Whiskers says: Meow!
(3) قيم الواجهة: النوع الديناميكي + القيمة الديناميكية
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)
}
الناتج:
nil: <nil>, <nil>
type=main.Dog, القيمة={Buddy}
5. الواجهة الفارغة interface{} وتأكيدات الأنواع
(1) واجهة فارغة: أي نوع
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"})
}
الناتج:
type=int, value=42
type=string, value=hello
type=float64, value=3.14
type=main.Dog, value={Buddy}
▶ مثال: تأكيد النوع (صيغة «الفاصلة مسموح بها»)
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)
}
الناتج:
String: hello (len=5)
Int: 42 (double=84)
Unknown type: float64 = 3.14
(3) مفتاح التبديل
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})
}
الناتج:
string: "hello" (len=5)
int: 42
float64: 3.14
bool: true
unknown: []int
(4) تأكيدات الأنواع مقابل مفاتيح الأنواع
| السيناريو | التوصية |
|---|---|
| التحقق مما إذا كان من نوع معين | تأكيد النوع v.(T) |
| تحديد أنواع متعددة | مفتاح التبديل v.(type) |
| ما عليك سوى التحقق (لا حاجة إلى إدخال قيمة) | _, ok := v.(T) |
6. تكوين الواجهة
(1) إنشاء واجهة جديدة عن طريق دمج واجهة موجودة
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")
}
▶ مثال: التطبيق العملي لتركيب الواجهات
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")
}
الناتج:
[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) أهم واجهتين في المكتبة القياسية
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
▶ مثال: تطبيق «Reader» بطرق مختلفة
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.
}
الناتج:
=== strings.Reader ===
read: "hello wo"
read: "rld"
(3) التركيبة io.Reader + io.Writer (سلسلة المكتبة القياسية)
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())
}
الناتج:
copied 19 bytes: "hello Go interfaces"
(4) قائمة الأنواع القياسية التي تُنفِّذ io.Reader
| النوع | الحزمة | تنفذ |
|---|---|---|
strings.Reader |
سلاسل | القارئ |
bytes.Reader |
بايت | القارئ |
os.File |
نظام التشغيل | قارئ + كاتب |
bytes.Buffer |
بايت | قارئ + كاتب |
net.Conn |
شبكة | قارئ + كاتب |
gzip.Reader |
ضغط/gzip | القارئ |
8. مثال كامل: تجريد بوابة الدفع المتعددة
دعونا نربط جميع المفاهيم الأساسية التي تناولناها في هذا الدرس معًا من أجل بناء نظام دفع متكامل:
// 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)
}
النتيجة المتوقعة:
[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 في PaymentService هو نوع واجهة، وليس نوعًا ملموسًا. يمكن لمتغير الواجهة أن يحتوي على أي قيمة تنفذ تلك الواجهة — وهذا هو الأساس الذي يستند إليه تنفيذ نمط الاستراتيجية في لغة Go.
❓ أسئلة شائعة
implements. وهذا يعني: (1) أن الأنواع الواردة من حزم الجهات الخارجية يمكنها أيضًا تنفيذ الواجهات التي تُعرِّفها؛ (2) أن النوع الواحد يمكنه تنفيذ عدة واجهات غير مرتبطة ببعضها على الإطلاق.interface{}؟fmt.Println(a ...interface{})؛ (2) تخزين قيم من أنواع مختلفة في خريطة: map[string]interface{}؛ (3) إزالة تسلسل البيانات (تحليل JSON إلى interface{}).v, ok := x.(T). لن يؤدي ذلك إلى حدوث حالة ذعر إذا كانت قيمة ok خاطئة. إذا لم تستخدم صيغة «comma-ok»، فإن فشل التأكيد سيؤدي إلى حدوث حالة ذعر: ستحدث حالة ذعر في v := x.(string) إذا لم تكن x سلسلة نصية.Read الخاصة بـ io.Reader القيمة EOF؟Read (0، io.EOF). لاحظ أن EOF يشير إلى نهاية عادية، وليس إلى خطأ — لذا لا يمكنك استخدام err != nil للتحقق من اكتمال القراءة؛ بل يجب عليك استخدام err == io.EOF.📖 ملخص
- الطريقة = دالة لها متلقي؛ ويمكن أن يكون المتلقي قيمة أو مؤشرًا
- يعمل المتلقي الذي يستقبل القيمة على نسخة، بينما يقوم المتلقي الذي يستقبل مؤشرًا بتعديل الكائن الأصلي
- الواجهة هي مجموعة من توقيعات الطرق؛ وتستخدم لغة Go التنفيذ الضمني (الكتابة حسب الشكل)
- تمثل الواجهة الفارغة
interface{}أي نوع - يعمل تأكيد النوع
v.(T)على استخراج القيمة الديناميكية للواجهة؛ بينما يمنعcomma-okحدوث حالة ذعر - يؤدي تكوين الواجهة إلى إنشاء واجهة جديدة عن طريق دمج الواجهات (
type A interface { B; C }) io.Readerوio.Writerهما الواجهتان الأساسيتان في المكتبة القياسية للغة Go- تضمن الواجهات أن يتبع الكود «مبدأ الانفتاح والإغلاق» — أي أن يكون مفتوحًا للتوسعة، ومغلقًا للتعديل
📝 تمارين
-
المسألة الأساسية (صعوبة ⭐): عرّف واجهة
Shape(Area() float64)، وقم بتنفيذ هيكلين —Circle(نصف القطر) وRectangle(العرض والارتفاع) — واحسب مساحتيهما واطبعها. -
مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ واجهة
Cache(Get(key string) (interface{}, bool)/Set(key string, value interface{}))، باستخدامmap[string]interface{}ونسخة محدودة الذاكرة (بحد أقصى 10 مفاتيح) لتنفيذ استراتيجيتين مختلفتين. يجب عليك استخدام مستقبلات المؤشرات. -
مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ خلفية تخزين قابلة للتوصيل: حدد واجهة
Store(Save(key string, data []byte) error/Load(key string) ([]byte, error)/Delete(key string) error)، وقم بتنفيذMemoryStore(تخزين الخرائط) وFileStore(تخزين الملفات باستخدامos.WriteFile/os.ReadFile)، وأخيرًا استخدمBackupServiceلمزامنة البيانات بين نوعي التخزين.