اصول SOLID در زبان Go (قسمت اول-Single Responsibility)


Single Responsibility principle

یا اصل تک وظیفه ای

سلام من علی فرهادنیا هستم،برنامه نویس و علاقه مند به دنیای تکنولوژی در این قسمت از این سری میخوام اولین اصل از اصول سالید رو در زبان گولنگ توضیح بدم.

این اصل توی دنیای شی گرایی برای کلاس ها بیان شده و به طور خلاصه میگه که هر کلاس باید یک مسئولیت داشته باشه ، برای یک چیز ایجاد شده باشه و به یک دلیل تغییر کنه.

ولی خب ما توی دنیای گولنگ کلاس ملاس نداریم :/

پس تکلیف چیه؟

خب اصلا کلاس چیه و چرا وجود داره؟

تعریف من از کلاس اینه که کلاس یه قالب کدنویسیه که یه سری چیزای مرتبطو کنار هم نگه میداره و بهشون یه ماهیت خاص و گاهی یه سری رفتار میده.

خب توی گولنگ یه چیزی وجود داره به نام type که با یه سینتکس به خصوص میشه انواع جدیدی از دادرو ایجاد کرد.

مثلا میشه به کمک ساختار داده ای به نام structure یه سری انواع داده مختلفو کنار هم قرار داد و بهشون یه ماهیت جدید داد.

و از جذابیتای زبون گولنگ اینه که میشه به کمک تابع ها برای این استراکچر ها یه سری رفتار تعریف کرد و یه سری تابعو بهشون نسبت داد.

پس تا حدی میشه این اصل تک وظیفه ایو گولنگیزش کرد:)

خب یه بازنویسی از این اصل برای دنیای گولنگ داشته باشیم:

اصل تک وظیفه ای در زبان گولنگ یعنی هر type باید یک مسئولیت داشته باشه ، برای یک چیز خاص ایجاد شده باشه و به یک دلیل تغییر کنه.

مقدمه بسه بریم ببینیم چه خبره:

با یه مثال شروع میکنم:

فرض کنید میخوایم یه دفترچه یادداشت (یا به قول خارجیا notepad) خیلی ساده برای خودمون بسازیم که هر وقت لازم شد بتونیم توش یه متن وارد کنیم و امکان حذف کردن متن هم داشته باشه.

به نظرم امکان ذخیره کردن متن توی فایل هم داشته باشه خوبه.شاید بعدا خواستیم وصلش کنیم به یه پرینتر و هر وقت خواستیم فایلارو پرینت بگیریم.

خب پس شد:

  • ساختاری برای نگه داری متن ها
  • قابلیت اضافه کردن متن
  • قابلین حذف متن
  • قابلیت ذخیره کردن توی فایل

بریم تو کار پیاده سازی:

برای ساختار دفترچه یادداشت از یه استراکچر ساده شامل یه آرایه از رشته ها استفاده میکنم:

//Notepad structure
type Notepad struct {
    notes []string //slice of strings to to keep notes
}


تابعی برای اضافه کردن نوشته به دفترچه یادداشت:

//AddNote get string and add it to notes
func (n *Notepad) AddNote(text string) {
    n.notes = append(n.notes, text)
}


تابعی برای حذف کردن یک یادداشت:

//RemoveNote get index and remove it from notes
func (n *Notepad) RemoveNote(index int) {
    n.notes =append(n.notes[:index], n.notes[index+1:]...)
}


یه تابعم برای چسبوندن همه یادداشت ها ب هم با فرمتی که مد نظرمونه به هم مینویسیم که برای پرینت کردنو و جاهای دیگه به درد میخوره(یه چیزی مثل تابع ToString جاوا):

//String join all notes and return them as one string
func (n *Notepad) String() string {
    return strings.Join(n.notes, &quot\n&quot)
}


و در آخر تابعی برای ذخیره کردن یادداشت ها توی فایل:

توجه:بعد از منتشر کردن لینک مقاله توی لینکدینم یکی از دوستان گفتن که پکیج io/ioutil هم از ورژن 1.16 به بعد کنار گذاشته شده پس من هم کامنتش میکنم و به جاش از پکیج os استفاده میکنم(همه تابع هایی که در ادامه از این پکیج استفاده کردنو تغییر میدم و کد قبلیو کامنت میکنم که مشخص باشه)

//function to save notes to file
func (n *Notepad) SaveToFile(path string) {
      _ = os.WriteFile(path, []byte(n.String()), 0644)
/*The io/ioutil package has turned out in go 1.16
     _ = ioutil.WriteFile(path,
    []byte(n.ToString()), 0644)
*/
}


خب تقریبا دفترچه یادداشتمونو درست کردیم حالا وقت امتحان کردنشه پس بیاین یه چنتا یادداشت بهش اضافه کنیم:

func main() {
    mynotes := Notepad{} //create notepad
    //adding note to notepad
    mynotes.AddNote(&quotcongratulations&quot)
    mynotes.AddNote(&quotBreaking the first principle was done successfully!&quot)
    //save notes to file
    mynotes.SaveToFile(&quottest1&quot)
    //print notes

   fmt.Println(mynotes.String())
}

خب خروجی کارو ببینیم:

congratulations
Breaking the first principle was done successfully!

خب ما همین الان اصل تک وظیفه ایو زیر پا گذاشتیم!

ما گفتیم این اصل میگه هر type که ایجاد میشه باید مسئول یک چیز باشه و خب دفترچه یادداشت مسئول نگه داری متنه نه نوشتن اون توی یه فایل دیگه .

پس تابع SaveToFile این اصلو زیر پا گذاشت چون به دفترچه یادداشت چسبوندیمش و گفتیم جزیی از اونه

خب راه حل چیه؟

سادست

راه حل اینه که تابع رو به صورت مستقل تعریف کنیم(حتی کلا میشه بردش توی یه پکیج دیگه)

اینجوری:

func SaveToFile(input string, path string) {
   _ = os.WriteFile(path, []byte(input), 0644)
/*The io/ioutil package has turned out in go 1.16
    _ = ioutil.WriteFile(path, []byte(input), 0644)
*/
}

برنامه که حالت قبلیم درست کار میکرد پس این کارا برای چیه؟

خب باید به عرضتون برسونم که درسته در این برنامه کوچیک همه چی به نظر درست میاد ولی فرض کنید برنامتون خیلی بزرگ تر میشه و مثلا همین تابع ذخیره در فایل به جز این دفترچه یادداشت خیلی جاهای دیگه هم کاربرد داره و باید برای هرجا که کاربرد داره بازم بنویسیمش و... که این موضوع علاوه بر زحمت زیاد در مرحله اولیه نوشتن میتونه یه فاجعه در مرحله گسترش دادن برنامه به همراه داشته باشه(اشک ها پای همین مسئله ریخته شده:))

این فقط یکی از دلایلشه و کلی دلیل دیگه هم داره که اگر خواستین بدونین به نظرم برین دنبال یادگیری مسائل مربوط به برنامه نویسی تمیز و اصولی(که البته فکر کنم کسی که داره این مقالرو میخونه داره دنبال همین میگرده:| ولی خب توضیح بیشتر در حوصله این بحث نمیگنجه).

در این مسئله حتی میشه کلا یه ماهیت جدا برای تابع ذخیره در فایل قائل شد و مثلا در اینده گسترش داد که کارهای دیگه ای مثل فرمت کردن یا ذخیره توی فضاهای ذخیره سازی ابری و ... رو پوشش بده:

//save structure
type Save struct {
    default_path    string
    //...
}
//function to save notes to file
func (s *Save) SaveToFile(input string, path string) {
    if path == &quot&quot {
        path = s.default_path
}
    _ = os.WriteFile(path, []byte(input), 0644)
/*The io/ioutil package has turned out in go 1.16
    _ = ioutil.WriteFile(path, []byte(input), 0644)
*/
}


از طریق گیتهابم میتونید به فایل کلی برنامه دسترسی داشته باشین.

از این لینک.

توی قسمت بعدی درباره اصل دوم صحبت میکنم.

بعد از انتشار قسمت دوم لینکشو اینجا قرار میدم.

ممنون به خاطر وقتی که گذاشتین.