الأساليب
الدرس 8: الأساليب
تشبيه من الواقع
تخيل أنك اشتريت سيارة. للسيارة وظائف متنوعة — تشغيل، تسارع، فرامل، تشغيل الأضواء. هذه الوظائف لا توجد بشكل مستقل؛ إنها تنتمي للسيارة. لن تقول "تشغيل" فعل عام؛ بل تقول "السيارة شُغّلت."
في Go، الأسلوب هو دالة مرتبطة بنوع معين. كما أن "التشغيل" ينتمي لـ "السيارة"، ينتمي الأسلوب للنوع المرتبط به. لن تضع دالة التشغيل على ثلاجة — بالمثل، تُعرّف الأساليب على النوع الذي تخدمه حقًا.
المفاهيم الأساسية
ما هو الأسلوب؟
الأسلوب هو دالة لها مستلم. المستلم يربط الأسلوب بنوع، مما يتيح لمتغيرات ذلك النوع استدعاء الأسلوب مباشرة.
المستلم
المستلم هو الجسر بين الأسلوب والنوع. تظهر صيغته بين كلمة func واسم الأسلوب:
func (receiverVariable ReceiverType) methodName(parameters) returnType {
// جسم الأسلوب
}
المستلم القيمي مقابل مستلم المؤشر
| الميزة | المستلم القيمي func (s Struct) |
مستلم المؤشر func (s *Struct) |
|---|---|---|
| يعمل على نسخة | نعم (التعديلات لا تؤثر على الأصل) | لا (يُعدّل الأصل مباشرة) |
| حالة الاستخدام | عمليات للقراءة فقط، تراكيب صغيرة | حاجة لتعديل الحقول، تراكيب كبيرة |
| تطبيق الواجهة | قابل للاستدعاء على القيم والمؤشرات | قابل للاستدعاء فقط على أنواع المؤشرات |
قواعد مجموعة الأساليب
- مجموعة أساليب نوع القيمة: تشمل فقط أساليب المستلم القيمي
- مجموعة أساليب نوع المؤشر: تشمل أساليب المستلم القيمي ومستلم المؤشر
هذا يعني: إذا كان لديك متغير من النوع *T، يمكنه استدعاء جميع أساليب T و *T؛ بينما متغير من النوع T يمكنه فقط استدعاء أساليب T.
التركيب بدل الوراثة
ليس في Go وراثة فئات؛ بل يتم تحقيق إعادة استخدام الكود عبر التركيب (التضمين). ضمّن تراكيب في أخرى، وأساليب التراكيب المضمنة تُرقى تلقائيًا إلى التراكيب الخارجية.
الصيغة الأساسية والاستخدام
تعريف الأساليب
package main
import "fmt"
// تعريف تراكيب
type Rectangle struct {
Width float64
Height float64
}
// أسلوب مستلم القيمة: حساب المساحة
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// أسلوب مستلم المؤشر: القياس (يحتاج لتعديل الحقول)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("Area:", rect.Area()) // الناتج: Area: 50
rect.Scale(2)
fmt.Println("Scaled area:", rect.Area()) // الناتج: Scaled area: 200
}
الفرق بين الأساليب والدوال
// هذه دالة، غير مرتبطة بأي نوع
func Area(r Rectangle) float64 {
return r.Width * r.Height
}
// هذا أسلوب، مرتبطة بنوع Rectangle
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
توريث الأساليب مع التراكيب المضمنة
package main
import "fmt"
// النوع الأساسي
type Animal struct {
Name string
}
// أسلوب Animal
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
// النوع الذي يضم Animal
type Dog struct {
Animal // تضمين، لا حاجة لاسم حقل
Breed string
}
// أسلوب Dog الخاص
func (d Dog) Bark() string {
return d.Name + " barks!"
}
func main() {
dog := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Golden Retriever",
}
// يمكن استدعاء أساليب النوع المضمن مباشرة
fmt.Println(dog.Speak()) // الناتج: Rex makes a sound
fmt.Println(dog.Bark()) // الناتج: Rex barks!
}
Area()، Scale()، Save()، IsValid().
func (r Rectangle) بدل func (rect Rectangle). هذه عادة Go.
sync.Mutex) أو عندما يكون التراكيب كبيرًا.
الأمثلة
مثال: تعريف أساسي للأساليب (الصعوبة ⭐)
عرّف تراكيب Circle ونفّذ أساليب لحساب المساحة والمحيط.
package main
import (
"fmt"
"math"
)
// تراكيب Circle
type Circle struct {
Radius float64
}
// Area يحسب المساحة (مستلم القيمة، عملية للقراءة فقط)
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// Perimeter يحسب المحيط
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// SetRadius يُعيّن نصف قطر جديد (مستلم المؤشر، يُعدّل الحقل)
func (c *Circle) SetRadius(r float64) {
if r > 0 {
c.Radius = r
}
}
func main() {
c := Circle{Radius: 5}
fmt.Printf("Radius: %.2f\n", c.Radius)
fmt.Printf("Area: %.2f\n", c.Area())
fmt.Printf("Perimeter: %.2f\n", c.Perimeter())
c.SetRadius(10)
fmt.Printf("\nNew radius: %.2f\n", c.Radius)
fmt.Printf("New area: %.2f\n", c.Area())
fmt.Printf("New perimeter: %.2f\n", c.Perimeter())
}
الناتج:
Radius: 5.00
Area: 78.54
Perimeter: 31.42
New radius: 10.00
New area: 314.16
New perimeter: 62.83
مثال: المستلم القيمي مقابل مستلم المؤشر (الصعوبة ⭐⭐)
يُظهر الفروقات السلوكية الجوهرية بين الاثنين.
package main
import "fmt"
// Account حساب بنكي
type Account struct {
Owner string
Balance float64
}
// المستلم القيمة: الحصول على الرصيد (للقراءة فقط، بدون تعديل)
func (a Account) GetBalance() float64 {
return a.Balance
}
// مستلم المؤشر: الإيداع (يحتاج لتعديل الرصيد)
func (a *Account) Deposit(amount float64) {
if amount > 0 {
a.Balance += amount
fmt.Printf(" Deposited %.2f, Balance: %.2f\n", amount, a.Balance)
}
}
// مستلم المؤشر: السحب
func (a *Account) Withdraw(amount float64) bool {
if amount > 0 && a.Balance >= amount {
a.Balance -= amount
fmt.Printf(" Withdrew %.2f, Balance: %.2f\n", amount, a.Balance)
return true
}
fmt.Printf(" Withdrawal of %.2f failed, insufficient balance\n", amount)
return false
}
// المستلم القيمة: تنسيق معلومات الحساب
func (a Account) String() string {
return fmt.Sprintf("Account[%s] Balance: %.2f", a.Owner, a.Balance)
}
func main() {
acc := Account{Owner: "Zhang San", Balance: 1000}
fmt.Println(acc)
fmt.Println("\n--- Transaction History ---")
acc.Deposit(500)
acc.Withdraw(200)
acc.Withdraw(2000) // رصيد غير كافٍ
fmt.Println("\n--- Final Status ---")
fmt.Println(acc)
fmt.Printf("Current balance: %.2f\n", acc.GetBalance())
}
الناتج:
Account[Zhang San] Balance: 1000.00
--- Transaction History ---
Deposited 500.00, Balance: 1500.00
Withdrew 200.00, Balance: 1300.00
Withdrawal of 2000.00 failed, insufficient balance
--- Final Status ---
Account[Zhang San] Balance: 1300.00
Current balance: 1300.00
مثال: التركيب بدل الوراثة (الصعوبة ⭐⭐⭐)
يُظهر توريث الأساليب عبر التراكيب المضمنة وتجاوز الأساليب.
package main
import "fmt"
// ==================== الطبقة الأساسية ====================
// Base تراكيب أساسي (مشابه "فئة أب")
type Base struct {
ID int
Name string
}
// Describe يُرجع وصفًا أساسيًا
func (b Base) Describe() string {
return fmt.Sprintf("Base[ID=%d, Name=%s]", b.ID, b.Name)
}
// Identify يُرجع معلومات التعريف
func (b Base) Identify() string {
return fmt.Sprintf("I am %s (ID: %d)", b.Name, b.ID)
}
// ==================== طبقة الأعمال ====================
// Employee يضم Base
type Employee struct {
Base // تضمين، يرث أساليب Base
Department string
Salary float64
}
// تجاوز أسلوب Describe الخاص بـ Base
func (e Employee) Describe() string {
return fmt.Sprintf("Employee[ID=%d, Name=%s, Dept=%s, Salary=%.0f]",
e.ID, e.Name, e.Department, e.Salary)
}
// أسلوب Employee الخاص
func (e Employee) AnnualSalary() float64 {
return e.Salary * 12
}
// Manager يضم Employee (تضمين متعدد المستويات)
type Manager struct {
Employee // تضمين Employee
TeamSize int
}
// تجاوز أسلوب Describe
func (m Manager) Describe() string {
return fmt.Sprintf("Manager[ID=%d, Name=%s, Dept=%s, Team=%d people]",
m.ID, m.Name, m.Department, m.TeamSize)
}
// أسلوب Manager الخاص
func (m Manager) TeamReport() string {
return fmt.Sprintf("%s manages a team of %d people", m.Name, m.TeamSize)
}
func main() {
// إنشاء كائن Base
b := Base{ID: 1, Name: "Base Object"}
fmt.Println("=== Base ===")
fmt.Println(b.Describe())
fmt.Println(b.Identify())
// إنشاء كائن Employee
emp := Employee{
Base: Base{ID: 2, Name: "Li Si"},
Department: "Engineering",
Salary: 15000,
}
fmt.Println("\n=== Employee ===")
fmt.Println(emp.Describe()) // يستدعي أسلوب Employee المتجاوز
fmt.Println(emp.Identify()) // مُورث من Base
fmt.Printf("Annual salary: %.0f\n", emp.AnnualSalary())
// إنشاء كائن Manager
mgr := Manager{
Employee: Employee{
Base: Base{ID: 3, Name: "Wang Wu"},
Department: "R&D",
Salary: 25000,
},
TeamSize: 8,
}
fmt.Println("\n=== Manager ===")
fmt.Println(mgr.Describe()) // يستدعي أسلوب Manager المتجاوز
fmt.Println(mgr.Identify()) // مُورث من Base عبر التضمين متعدد المستويات
fmt.Printf("Annual salary: %.0f\n", mgr.AnnualSalary()) // مُورث من Employee
fmt.Println(mgr.TeamReport()) // أسلوب Manager الخاص
}
الناتج:
=== Base ===
Base[ID=1, Name=Base Object]
I am Base Object (ID: 1)
=== Employee ===
Employee[ID=2, Name=Li Si, Dept=Engineering, Salary=15000]
I am Li Si (ID: 2)
Annual salary: 180000
=== Manager ===
Manager[ID=3, Name=Wang Wu, Dept=R&D, Team=8 people]
I am Wang Wu (ID: 3)
Annual salary: 300000
Wang Wu manages a team of 8 people
سيناريوهات من الواقع
السيناريو 1: نظام مصادقة المستخدم
استخدام الأساليب لتجسيد منطق مصادقة المستخدم.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
)
// User تراكيب المستخدم
type User struct {
Username string
PasswordHash string
Email string
CreatedAt time.Time
IsActive bool
LoginCount int
}
// NewUser يُنشئ مستخدمًا جديدًا (نمط المُنشئ)
func NewUser(username, password, email string) *User {
return &User{
Username: username,
PasswordHash: hashPassword(password),
Email: email,
CreatedAt: time.Now(),
IsActive: true,
LoginCount: 0,
}
}
// hashPassword يُجزئ كلمة المرور (دالة مستوى الحزمة، ليست أسلوبًا)
func hashPassword(password string) string {
h := sha256.Sum256([]byte(password))
return hex.EncodeToString(h[:])
}
// VerifyPassword يتحقق من كلمة المرور
func (u *User) VerifyPassword(password string) bool {
return u.PasswordHash == hashPassword(password)
}
// Login دخول المستخدم
func (u *User) Login(password string) error {
if !u.IsActive {
return fmt.Errorf("account %s has been disabled", u.Username)
}
if !u.VerifyPassword(password) {
return fmt.Errorf("incorrect password")
}
u.LoginCount++
return nil
}
// Deactivate يُعطّل الحساب
func (u *User) Deactivate() {
u.IsActive = false
}
// Info يُرجع معلومات المستخدم
func (u User) Info() string {
status := "Active"
if !u.IsActive {
status = "Disabled"
}
return fmt.Sprintf("[%s] %s | Email: %s | Status: %s | Login count: %d",
u.Username, u.Username, u.Email, status, u.LoginCount)
}
func main() {
user := NewUser("zhangsan", "mySecret123", "zhangsan@example.com")
fmt.Println("Created user:")
fmt.Println(user.Info())
// الدخول بكلمة مرور صحيحة
fmt.Println("\nLogin with correct password:")
err := user.Login("mySecret123")
if err != nil {
fmt.Println("Login failed:", err)
} else {
fmt.Println("Login successful!")
}
fmt.Println(user.Info())
// الدخول بكلمة مرور خاطئة
fmt.Println("\nLogin with wrong password:")
err = user.Login("wrongPassword")
if err != nil {
fmt.Println("Login failed:", err)
}
// محاولة الدخول بعد تعطيل الحساب
fmt.Println("\nTry login after disabling account:")
user.Deactivate()
err = user.Login("mySecret123")
if err != nil {
fmt.Println("Login failed:", err)
}
fmt.Println(user.Info())
}
ناتج عينة:
Created user:
[zhangsan] zhangsan | Email: zhangsan@example.com | Status: Active | Login count: 0
Login with correct password:
Login successful!
[zhangsan] zhangsan | Email: zhangsan@example.com | Status: Active | Login count: 1
Login with wrong password:
Login failed: incorrect password
Try login after disabling account:
Login failed: account zhangsan has been disabled
[zhangsan] zhangsan | Email: zhangsan@example.com | Status: Disabled | Login count: 1
السيناريو 2: نظام سلة التسوق
استخدام الأساليب لتنفيذ عمليات السلة (إنشاء، قراءة، تحديث، حذف) والخروج.
package main
import "fmt"
// Product منتج
type Product struct {
Name string
Price float64
Category string
}
// CartItem عنصر السلة
type CartItem struct {
Product Product
Quantity int
}
// Subtotal يحسب المجموع الفرعي
func (ci CartItem) Subtotal() float64 {
return ci.Product.Price * float64(ci.Quantity)
}
// ShoppingCart سلة التسوق
type ShoppingCart struct {
Items []CartItem
Owner string
}
// Add يُضيف منتجًا إلى السلة
func (sc *ShoppingCart) Add(p Product, qty int) {
// التحقق مما إذا كان موجودًا
for i := range sc.Items {
if sc.Items[i].Product.Name == p.Name {
sc.Items[i].Quantity += qty
fmt.Printf(" Updated quantity: %s x%d\n", p.Name, sc.Items[i].Quantity)
return
}
}
sc.Items = append(sc.Items, CartItem{Product: p, Quantity: qty})
fmt.Printf(" Added product: %s x%d\n", p.Name, qty)
}
// Remove يُزيل منتجًا من السلة
func (sc *ShoppingCart) Remove(name string) bool {
for i, item := range sc.Items {
if item.Product.Name == name {
sc.Items = append(sc.Items[:i], sc.Items[i+1:]...)
fmt.Printf(" Removed product: %s\n", name)
return true
}
}
fmt.Printf(" Product not found: %s\n", name)
return false
}
// Total يحسب السعر الإجمالي
func (sc ShoppingCart) Total() float64 {
var total float64
for _, item := range sc.Items {
total += item.Subtotal()
}
return total
}
// Count يُرجع عدد أنواع المنتجات
func (sc ShoppingCart) Count() int {
return len(sc.Items)
}
// Display يطبع محتويات السلة
func (sc ShoppingCart) Display() {
fmt.Printf("\n🛒 %s's cart (%d products):\n", sc.Owner, sc.Count())
fmt.Println(" ─────────────────────────────────────")
for _, item := range sc.Items {
fmt.Printf(" %-12s $%.2f x %d = $%.2f\n",
item.Product.Name, item.Product.Price, item.Quantity, item.Subtotal())
}
fmt.Println(" ─────────────────────────────────────")
fmt.Printf(" Total: $%.2f\n", sc.Total())
}
func main() {
cart := ShoppingCart{Owner: "Zhang San"}
// تعريف المنتجات
laptop := Product{Name: "Laptop", Price: 999, Category: "Electronics"}
mouse := Product{Name: "Wireless Mouse", Price: 29, Category: "Electronics"}
book := Product{Name: "Go Programming", Price: 45, Category: "Books"}
coffee := Product{Name: "Coffee", Price: 5, Category: "Food"}
// إضافة المنتجات
fmt.Println("Adding products:")
cart.Add(laptop, 1)
cart.Add(mouse, 2)
cart.Add(book, 3)
cart.Add(coffee, 5)
cart.Display()
// تحديث الكميات
fmt.Println("\nUpdating quantities:")
cart.Add(mouse, 1) // إضافة فأرة واحدة أخرى
cart.Add(book, -1) // إزالة كتاب واحد (عبر رقم سالب)
cart.Display()
// إزالة منتج
fmt.Println("\nRemoving product:")
cart.Remove("Coffee")
cart.Display()
}
الناتج:
Adding products:
Added product: Laptop x1
Added product: Wireless Mouse x2
Added product: Go Programming x3
Added product: Coffee x5
🛒 Zhang San's cart (4 products):
─────────────────────────────────────
Laptop $999.00 x 1 = $999.00
Wireless Mouse $29.00 x 2 = $58.00
Go Programming $45.00 x 3 = $135.00
Coffee $5.00 x 5 = $25.00
─────────────────────────────────────
Total: $1217.00
Updating quantities:
Updated quantity: Wireless Mouse x3
Updated quantity: Go Programming x2
🛒 Zhang San's cart (4 products):
─────────────────────────────────────
Laptop $999.00 x 1 = $999.00
Wireless Mouse $29.00 x 3 = $87.00
Go Programming $45.00 x 2 = $90.00
Coffee $5.00 x 5 = $25.00
─────────────────────────────────────
Total: $1201.00
Removing product:
Removed product: Coffee
🛒 Zhang San's cart (3 products):
─────────────────────────────────────
Laptop $999.00 x 1 = $999.00
Wireless Mouse $29.00 x 3 = $87.00
Go Programming $45.00 x 2 = $90.00
─────────────────────────────────────
Total: $1176.00
❓ أسئلة شائعة
س1: كيف أختار بين المستلم القيمي ومستلم المؤشر?
اتبع هذه القواعد:
- تحتاج لتعديل حقول المستلم → مستلم المؤشر
- المستلم تراكيب كبير (عديد الحقول أو مصفوفات كبيرة) → مستلم المؤشر (يتجنب تكلفة النسخ)
- التراكيب يحتوي حقول غير قابلة للنسخ مثل
sync.Mutex→ يجب استخدام مستلم المؤشر - عملية للقراءة فقط والتراكيب صغير → المستلم القيمي كافٍ
- عند الشك → فضّل مستلم المؤشر
type Small struct { X int }
func (s Small) Get() int { return s.X } // ✅ مستلم القيمة، للقراءة فقط
type Large struct { Data [1024]byte }
func (l *Large) Process() { /* ... */ } // ✅ مستلم المؤشر، يتجنب النسخ
type Safe struct { mu sync.Mutex }
func (s *Safe) Lock() { s.mu.Lock() } // ✅ يجب استخدام المؤشر، Mutex غير قابل للنسخ
س2: لماذا يُصدر المترجم خطأ عند استدعاء أسلوب؟
السبب الأكثر شيوعًا هو التبديل بين اصطلاحات استدعاء المستلم القيمي ومستلم المؤشر.
type Dog struct { Name string }
func (d *Dog) Rename(name string) {
d.Name = name
}
func main() {
d := Dog{Name: "Rex"}
d.Rename("Buddy") // ✅ Go يأخذ العنوان تلقائيًا، يعادل (&d).Rename("Buddy")
// لكن هذا سيُسبب خطأ:
// Dog{"Rex"}.Rename("Buddy") // ❌ لا يمكن أخذ عنوان قيمة غير قابلة للعنونة
}
مترجم Go يتعامل تلقائيًا مع تحويل d.Rename() → (&d).Rename()، لكن فقط إذا كان المتغير قابلًا للعنونة. القيم الحرفية والمؤقتة غير قابلة للعنونة.
س3: هل الأساليب يمكن أن تُرجع عدة قيم؟
نعم، الأساليب والدوال لها قواعد توقيع متطابقة ويمكن أن يكون لها أي عدد من المعلمات والقيم المرجعة.
type Calculator struct {
Value float64
}
// يُرجع حاصل القسمة والباقي
func (c Calculator) DivideBy(divisor float64) (float64, float64, error) {
if divisor == 0 {
return 0, 0, fmt.Errorf("divisor cannot be zero")
}
return c.Value / divisor, math.Mod(c.Value, divisor), nil
}
س4: ماذا عن تعارض الأساليب مع التراكيب المضمنة؟
عندما يكون لنوعين مضمنين أساليب بنفس الاسم، سيُصدر مترجم Go خطأ، وتحتاج لتحديد أيهما تستدعي صراحةً.
type A struct{}
func (A) Hello() string { return "A" }
type B struct{}
func (B) Hello() string { return "B" }
type C struct {
A
B
}
func main() {
c := C{}
// c.Hello() // ❌ خطأ ترجمة: مُحدِّد غامض c.Hello
fmt.Println(c.A.Hello()) // ✅ صريح: يُخرج "A"
fmt.Println(c.B.Hello()) // ✅ صريح: يُخرج "B"
}
📖 ملخص
في هذا الدرس تعلمنا عن الأساليب في Go:
- تعريف الأسلوب: ربط الدوال بالأنواع عبر المستلمات
- المستلم القيمي مقابل مستلم المؤشر: المستلمات اليمية تعمل على نسخ؛ مستلمات المؤشر تُعدّل القيم الأصلية
- قواعد مجموعة الأساليب: أنواع القيم لها فقط أساليب المستلم القيمي؛ أنواع المؤشرات لها جميع الأساليب
- التركيب بدل الوراثة: تحقيق إعادة استخدام الأساليب وتجاوزها عبر التراكيب المضمنة
- أفضل الممارسات: أبقِ أسماء المستلمات قصيرة، استخدم أنواع مستلمات متسقة لنفس النوع، استخدم المؤشرات عند الحاجة للتعديل
الأساليب هي أساس البرمجة الكائنية في Go. الدرس التالي يتناول الواجهات — أداة Go القوية للتعددية الشكلية، التي تعمل بشكل وثيق مع الأساليب لتشكيل جوهر نظام أنواع Go.
📝 تمارين
تمرين 1: محوّل درجة الحرارة ⭐
أنشئ تراكيب Temperature بحقل Celsius، ونفّذ الأساليب التالية:
To Fahrenheit()— تحويل إلى فهرنهايت (F = C × 9/5 + 32)To Kelvin()— تحويل إلى كلفن (K = C + 273.15)Describe() string— إرجاع وصف منسق لدرجة الحرارة
// الناتج المتوقع:
// Temperature: 100.00°C = 212.00°F = 373.15K
تمرين 2: حساب بنكي (مع سجل المعاملات) ⭐⭐
وسّع تراكيب Account من السيناريو 2 بالميزات التالية:
- سجل المعاملات (شريحة تخزن مبلغ كل معاملة والوقت)
- أسلوب
History()لطباعة جميع سجلات المعاملات - أسلوب
Statement()لإنشاء كشف حساب منسق
// الناتج المتوقع:
// === Account Statement ===
// Account: Zhang San
// Transaction history:
// 1. +1000.00 (Opening deposit)
// 2. -200.00 (Purchase)
// 3. +500.00 (Salary)
// Current balance: 1300.00
تمرين 3: نظام الأشكال (وراثة التركيب) ⭐⭐⭐
أنشئ نظام أشكال:
- عرّف واجهة
Shapeأساسية بأساليبArea() float64وPerimeter() float64 - نفّذ تراكيب
RectangleوCircleوTriangle - أنشئ تراكيب
Canvasيمكنها احتواء عدة أشكال - نفّذ
Canvas.TotalArea()لحساب المساحة الكلية لجميع الأشكال - استخدم التراكيب المضمنة لإنشاء
ColoredRectangle، الذي له أساليب Rectangle وحقلColor
// الناتج المتوقع:
// Canvas has 3 shapes
// Total area: xxx.xx
// Red rectangle: width=10, height=5, color=red
الدرس التالي
الدرس 9: الواجهات — تعلّم عن نظام الواجهات في Go: التطبيق الضمني، الواجهة الفارغة، تأكيدات النوع، تركيب الواجهات، وأقوى آليات التعددية الشكلية في Go.



