ترکیب به جای وراثت در زبان Go

زبان برنامه نویسی Go با به کار گیری از یک تایپ سیستم انعطاف پذیر،امکان استفاده مجدد از کد رو با کمترین میزان تلاش در اختیار برنامه نویس قرار میدهد. البته که زبان Go هنوز هم یک زبان شئ گراست ولی بدون مشکلات همیشگی این زبانها!
اگر شماهم چندین روز رو درگیر برنامه ریزی برای کلاسهای abstract و interface های مرتبط به اونها در یک برنامه پیچیده جاوا و یا ++C بوده باشید، از سادگی تایپ سیستم Golang لذت خواهید برد.

برنامه نویسهای Go با استفاده از الگوی طراحی Composition به سادگی میتوانند یک تایپ رو به صورت embed در یک تایپ دیگر وارد کنند تا بتوانند از تمام امکانات تایپ مبدا استفاده کنند، البته به خاطر داشته باشیم که زبان های برنامه نویسی دیگری هم از Composition استفاده میکنند ولی اغلب آنها عمیقا به ارث بری وابسته اند که استفاده از این امکان رو در این زبان ها مشکل و پیچیده میکنه.

در زبان Go تایپها ترکیب شده از تایپ های کوچکترند که در تضاد با مدل های سنتی ارث بری هستند.

خوب برای اینکه با این مفهوم بهتر آشنا بشیم با مثال همیشگی Duck شروع میکنیم.

type Duck struct {
    ID int64
    Name string
}

حالا میتونیم برای Duck struct یک متود هم برای خوردن به شکل زیر تعریف کنیم.

func (d *Duck) Eat() {
    fmt.Println(&quotDuck %s eats!&quot, d.Name)
}

بسیار عالی! اگه در نظر داشته باشیم یه struct غاز وحشی هم داشته باشیم که خیلی شبیه به اردک باشه ولی رنگ های مختلفی دارند و قراره رنگهاشون رو هم ذخیره کنیم چی ؟ زبان Go ارث بری نداره ولی از ترکیب (Composition) پشتیبانی می‌کند

type Mallard struct {
    Duck
    Color string
}

با Embed کردن استراکچر Duck در Mallard میتونیم به خصیصه های `ID` و `Name` دسترسی پیدا کنیم

duck := new(Duck)
duck.ID = 1
duck.Name = &quotRick&quot

mallard := new(Mallard)
mallard.Color = &quotGreen&quot

// copy info:
mallard.Name = duck.Name
mallard.ID = duck.ID

// or even better
mallard.Duck = *duck

البته که برای Mallard struct هم میتونیم متدهای مختص به خودش رو ایجاد کنیم.

func (m *Mallard) Sleep() {
    fmt.Println(&quot%s Mallard %s sleeps!&quot,m.Color, m.Name)
}

حالا با اینکار Mallard ما هم میتونه غذا بخوره و هم میتونه بخوابه !

mallard.Eat()
malled.Sleep()

// Duck Ruck eats!
// Green Mallard Rick sleeps!

اگه به چشم شئ گرایی به نتیجه نگاه کنیم، خروجی کار خیلی شبیه به ارث بری به نظر میرسه ولی در اصل اجرای اون کاملا بر اساس قاعده ترکیب یا composition هست.

به قول یکی از دوستان خوبم که میگه :

اگه میخواهی یک زبان رو به روش درست درک کنی بهتره به نحوه توسعه پکیج ها و کامپوننت های داخلی همون زبان نگاه کنی !

و برای زبان Golang این نقل قول خیلی کارایی داره!

یکی از روشهای خیلی متداول در پکیج های داخلی Golang برای lock کردن ساختارهای داده ها استفاده از همین الگوی طراحی composition هست برای مثال در کتابخانه cache ساختار hashDebug درون خود sync.Mutex را ضمیمه کرده پس هرجا برای تغییر مپ m نیاز به قفل کردن وجود داشته باشد کافیه با اجرای دستور hashDebug.Lock از قابلیت Lock که به واسطه sync.Mutex به دست آورده استفاده کنه.

// hashDebug holds the input to every computed hash ID,
// so that we can work backward from the ID involved in a
// cache entry mismatch to a description of what should be there.
var hashDebug struct {
   sync.Mutex
   m map[[HashSize]byte]string
}

// reverseHash returns the input used to compute the hash id.
func reverseHash(id [HashSize]byte) string {
   hashDebug.Lock()
   s := hashDebug.m[id]
   hashDebug.Unlock()
   return s
}