بسیاری از زبانهای برنامهنویسی مدرن همچون جاوا، پایتون، سیشارپ، پیاچپی و ... از شیئگرایی پشتیبانی میکنند اما در عین حال برخی زبانها هستند که میشود برچسب فانکشنال روی آنها زد و خیلی پایبند به اصول شیئگرایی نیستند که از آن جمله میشود به راست، اِلکسیر، جاوااسکریپت و ... اشاره کرد. در این دورهٔ آموزشی، تمرکز روی زبانهای گروه اول و اختصاصاً زبان پیاچپی بوده و پیش از هر چیز به یک معرفی کوتاه از مفهوم شیئگرایی پرداخته سپس ویژگیهای کلیدی این متودولوژی را ذکر خواهیم نمود.
OOP (شیئگرایی) چیست؟
طراحان OOP با الهام از اشیاء دنیای واقعی و نوع ارتباط مابین آنها، اقدام به پایهریزی این سَبک توسعهٔ نرمافزار نمودند؛ با این تفاسیر، در اواوپی هر چیزی یک Object (شیئ) است و اساساً آبجکتها نمونهای از مفهومی تحت عنوان Class هستند و کلاس نیز حاوی یک سری خصیصهها و رفتارهای انتزاعی است که به خودیِ خود کاری از پیش نمیبرند اما زمانی که یک آبجکت از روی آن ساخته شود، نمود عینی پیدا کرده و کاربردی میشود. با این تفاسیر، در شیئگرایی دو مفهوم اصلی داریم که عبارتند از:
- Class: یک طرح کلی (همچون نقشهٔ یک خانه) است که دربرگیرندهٔ ویژگیها و تَسکهایی میباشد که یک آبجکت میتواند انجام دهد.
- Object: یک نمونهٔ عینی ساخته شده از روی یک کلاس است که میتواند حاوی یک سری پراپرتی، متد، دیتا استراکچر و ... باشد.
برای درک بهتر این موضوع، میتوان «نوعِ بشر» را همچون یک کلاس در نظر گرفت. اساساً آدمها دارای یک سری خصوصیات از جمله قد، وزن، رنگ چشم و ... بوده مضاف بر این که یک سری رفتارها هم دارند که از آن جمله میتوان به توانایی راه رفتن، صحبت کردن، غذا خوردن و دیگر کارهایی از این دست اشاره کرد. حال این «کلاسِ نوعِ بشر» میتواند در یک انسان واقعی عینیت پیدا کند. به طور مثال، بهزاد یک آبجکت ساختهشده از روی «کلاسِ نوعِ بشر» است که دارای خصوصیاتی مثل قد ۱۸۳ سانتیمتر، وزن ۹۰ کیلوگرم و رنگ چشم قهوهای است به علاوه این که از رفتارهایی که برای یک کلاسِ نوعِ بشر برشمردیم نیز برخوردار است به طوری که میتواند راه برود، صحبت کند و غذا بخورد (علاوه بر نگارندهٔ این آموزش، خوانندهٔ این مطلب نیز یک آبجکت ساختهشده از روی چنین کلاسی است!)
در تکمیل توضیحات فوق، لازم به یادآوری است که هر کلاس حداقل از دو مفهوم Attribute و Behavior برخوردار است. به عنوان مثالی دیگر، اگر فرض کنیم کلاسی داریم تحت عنوان Car
که قصد داریم یک سری خودرو از روی آن بسازیم، این کلاس حاوی یک سری Attribute یا «خصیصه» خواهد بود که از آن جمله میتوان به رنگ، مدل، سال ساخت، قیمت و ... اشاره کرد؛ همچنین این کلاس که به منزلهٔ نقشهای برای ساخت خودروهای واقعی است، حاوی یک سری Behavior یا «رفتار» نیز میباشد که از آن جمله میشود به قابلیتهایی همچون گاز دادن، ترمز کردن، استارت زدن و ... اشاره کرد (قد، وزن و رنگ چشم در مثال قبل Attribute هستند و Behavior نیز به توانایی راه رفتن، صحبت کردن، غذا خوردن اشاره دارد.)
به خاطر داشته باشید توجه داشته باشیم که در متودولوژی شیئگرایی به جای Attribute از نام دیگری همچون Property و به جای Behavior از اسامی دیگری همچون Function یا Method استفاده میشود.
حال که با مفاهیم کلاس و آبجکت آشنا شدیم، نیاز به بررسی چهار اصل کلیدی در شیئگرایی میرسد که میتوان آنها در قالب عبارت «A PIE» به خاطر سپرد که عبارتند از:
- Abstraction
- Polymorphism
- Inheritance
- Encapsulation
آشنایی با مفهوم Abstraction
شیئگرایی بیشتر در نرمافزارهای بزرگ و پیچیده استفاده میشود و نیاز به توضیح نیست که این دست نرمافزارها در طول زمان حجیم و حجیمتر میشوند و چنانچه معماری درستی در توسعهٔ آنها به کار گرفته نشده باشد، در نهایت مدیریتشان بسیار دشوار و گاهی هم ناممکن میگردد! اساساً میتوان گفت که Abstraction راهکاری برای رفع این معضل است بدان معنا که تا حد ممکن جزئیات هر کلاس میباید از نظر پنهان باشند (نام دیگر این مفهوم Data Abstraction است.) برای درک بهتر این موضوع، میتوان یک آبمیوهگیری را مثال زد که پشت پرده ساختار به نسبت پیچیدهای دارد اما ما به عنوان یک کاربر، صرفاً هویج را داخل آن ریخته و آبش تحویل میگیریم. در واقع، جزئیات کار این دستگاه انتزاعی بوده و اساساً نیازی هم نداریم تا نسبت به آنها آگاه باشیم.
در شیئگرایی هم داستان تا حدودی از همین قرار است؛ به عبارتی، یک کلاس از یک سری متد برخوردار است که هر کدام الگوریتم خاص خود را دارند و زمانی که سایر کلاسها اقدام به استفاده از متدهای کلاس مذکور میکنند، هرگز نیازی نخواهند داشت تا نسبت به الگوریتمهای نوشتهشده داخل هر متد آگاهی داشته باشند؛ بلکه صرفاً آنها را استفاده نموده و نتیجهٔ مد نظر خود را دریافت میکنند.
به عنوان مثالی دیگر، اگر فرض را بر این بگذاریم که کلاسی داریم به نام Student
، این کلاس حاوی یک سری پراپرتی خواهد بود تحت عناوین enrolmentNumber
،name
و ... در حالی که یک سری پراپرتیهای خاص از جمله pulseRate
و sizeOfShoe
در این کلاس گنجانده نخواهند شد چرا که اساساً ربطی به فضای آموزشی که یک دانشآموز داخل آن خواهد بود ندارند. به عنوان مثال آخر در این ارتباط، اگر کلاسی داشته باشیم تحت عنوان Email
که وظیفهٔ ارسال ایمیل را داشته باشد و از آن در کلاس دیگری همچون User
استفاده کرده باشیم، این که سازوکار کلاس Email
چگونه است و ارسال ایمیل به چه شکل صورت میگیرد اصلاً برای کلاس User
محلی از اِعراب ندارد بلکه این کلاس صرفاً نیاز دارد تا برای یک کاربر خاص ایمیلی با محتوای اختصاصی ارسال کند و همین که چنین کاری صورت گیرد برایش کفایت میکند و دیگر به مسائل حاشیهای مثل این که نحوهٔ درج عنوان ایمیل، محتوای ایمیل، پروتکل مورد استفاده و ... چگونهاند توجه نمیکند.
در پایان، اگر به مثال «کلاسِ نوعِ بشر» باز گردیم، ویژگیهایی مثل قد و وزن یا توانایی راه رفتن و صحبت کردن این کلاس در معرض دید سایرین قرار میگیرد اما بر اساس قانون Abstraction، چیزهایی از جمله جزئیات نورونهای مغز و یا چگونگی ارتباط آنها با یکدیگر پنهان هستند. در یک کلام، Abstraction بیش از آن که به سازوکار داخلی یک کلاس/آبجکت بپردازد، روی کاری که آن آبجکت انجام میدهد متمرکز است و این خصیصه در توسعهٔ نرمافزارهای بزرگ و پیچیده منجر به سادگی بیشتر و بالتبع سهولت نگهداری پروژه میگردد.
آشنایی با مفهوم Polymorphism
واژهٔ Poly در زبان یونانی به معنی «چند» است و Morph نیز به معنای «شکل» است که روی هم رفته میتوان معادلی همچون «چندشکلی/چندریختی» برایش در نظر گرفت. برای درک بهتر Polymorphism، نیاز است تا با مفهومی در شیئگرایی آشنا گردیم به نام اینترفیس که این امکان را برایمان فراهم میآورد تا یک ساختار کلی طراحی نموده سپس کلاسهای مد نظرمان را موظف به تبعیت از آن ساختار کنیم اما در عین حال هر کلاس میتواند پیادهسازی خاص خود را از آن ساختار داشته باشد؛ به عبارتی، آن ساختار کلی میتواند به اَشکال مختلفی پیادهسازی شود و این همان مفهوم چندشکلی است.
اساساً وقتی صحبت از Polymorphism به میان میآید، منظور آن است که یک آبجکت میتواند حداقل در دو فرم مختلف نمود عینی پیدا کند. به عبارتی، این ویژگی به ما اجازه میدهد تا اقدام به بازآفرینی چگونگی کارکرد یک چیز کنیم که این کار از طریق مفاهیمی تحت عناوین Overloading و Overriding عملی میگردد. اگر در مثال «کلاسِ نوعِ بشر» فرض را بر این بگذاریم که به جای پا با دستان خود راه برویم، پروسهٔ راه رفتن در فرم دیگری نسبت به فرم/شکل مرسوم صورت گرفته که این کار Overloading گفته میشود. در عین حال، اگر در چنین کلاسی برای راه رفتن کماکان از پاهای خود استفاده کنیم اما نحوهٔ استفادهٔ ما از پاها به شکل متفاوتی نسبت به روش مرسوم باشد، این کار Overriding خصیصههای «کلاسِ نوعِ بشر» قلمداد میگردد.
در تکمیل توضیحات فوق و به عنوان یک مثال تکمیلی، میتوان به مفهوم کلی نوشتافزار اشاره کرد که ابزاری به منظور نوشتن چیزی روی کاغذ میباشد و این در حالی است که برای این منظور میتوانیم از ابزارهایی همچون خودنویس، مداد و یا خودکار استفاده نماییم؛ در عین حال، ما از هر نوع نوشتافزاری انتظار داریم که به خوبی داخل دست جای گرفته و بتوان از طریق آن چیزی را نوشت و خروجی تمام آنها یک کار است و آن هم چیزی نیست جز این که قادر خواهیم بود تا نوشتهای را روی کاغذ بیاوریم اما نحوهٔ کار آنها با در چند شکل متفاوت صورت میگیرد.
آشنایی با مفهوم Inheritance
در شیئگرایی مواقعی پیش میآید که آبجکتها برخی خصوصیاتشان مشابه یکدیگر است اما در عین حال یک سری تفاوتها هم با همدیگر دارند و اینجا است که به منظور جلوگیری از دوبارهکاری و نوشتن کدهای تکراری، میتوانیم از مفهومی تحت عنوان Inheritance به معنای «وراثت» بهره بگیریم. به کلام سادهتر، میتوان سازوکاری اندیشید تا یک کلاس برخی خصوصیاتش را از کلاس دیگری به ارث ببرد اما در عین حال ویژگیهای خاص خود را هم داشته باشد که در چنین فضایی، تا حد ممکن از دوبارهکاری جلوگیری به عمل خواهد آمد.
ایدهٔ وراثت به نوعی برگرفته از دنیای واقعی است. در واقع، همانطور که فرزند برخی خصوصیات خود را از والد به ارث میبرد، یک کلاس زیرشاخه که معمولاً تحت عنوان Child Class شناخته میشود نیز میتواند برخی خصوصیاتش را از کلاس اصلی ارثبری کند که اصطلاحاً Parent Class نام دارد. به طور کلی، انواع مختلفی از وراثت در شیئگرایی داریم که عبارتند از:
- Single Inheritance: مثالی که در بالا در ارتباط با وراثت زدیم به این نوع باز میگردد؛ به عبارتی، یک کلاس فرزند داریم که کلیهٔ خصوصیاتش را از کلاس والد به ارث میبرد.
- Multi-level Inheritance: در این مدل از وراثت، خود کلاس والد نیز میتواند کلاس والد دیگری داشته باشد. به بیانی بهتر، کلاس فرزند خصوصیات خود را از کلاس پدر به ارث میبرد و کلاس پدر نیز خصوصیاتش را از کلاس پدربزرگ به ارث خواهد برد تا جایی که میتوان گفت کلیهٔ خصوصیات پدربزرگ در کلاس فرزند نیز موجود هستند.
- Multiple Inheritance: وراثت چندگانه در خیلی از زبانها مثل پیاچپی ساپورت نمیشود و حاکی از آن است که یک کلاس فرزند خصوصیاتش را از بیش از یک کلاس والد به ارث ببرد.
- Hierarchical Inheritance: اساساً چنانچه بیش از یک کلاس (فرزند) از کلاس والد ارثبری کنند، گفته میشود که مدل وراثت Hierarchical است. به طور مثال، در اپ WhatsApp یک پیام میتواند در قالب متن، صوت و یا تصویر ارسال گردد؛ مثلاً فرض کنیم که یک کلاس Message
داریم که کلاسهای فرزندی همچون AudioMessage
،TextMessage
و PhotoMessage
از آن ارثبری میکنند.
آشنایی با مفهوم Encapsulation
این اصطلاح از فعل Encapsulate گرفته شده که میتوان معادلی همچون «در محفظه قرار دادن» برایش در نظر گرفت. در واقع، در اواوپی (شیئگرایی) این اصطلاح زمانی مصداق پیدا میکند که سایر آبجکتها به وضعیت یک آبجکت یا شیئ دسترسی نداشته باشند؛ گویی آبجکت مذکور داخل یک کپسول قرار گرفته و از هر گونه اِعمال تغییر محافظت خواهد شد. با در نظر گرفتن چنین فیچری در شیئگرایی، دیگر بخشهای نرمافزار اجازهٔ دخلوتصرف در یک آبجکت را ندارند مگر آن که صراحتاً چنین اجازهای به آنها داده شده باشد که چنین فیچری در نهایت منجر به Data Integrity شده به طوری که دادهها به سادگی دستخوش تغییرات سهوی یا عمدی نخواهند شد.
به عنوان مثالی از دنیای واقعی، میتوان تلفن همراه را مثال زد. زمانی که تصمیم میگیریم تا با یکی از کانتکتهای گوشی خود تماس حاصل نماییم، شمارهٔ وی را یافته و دکمهٔ تماس را زده و شروع به صحبت کردن میکنیم و دیگر به این که در مخابرات چه اتفاقی میافتد که میتوانیم با فردی دیگر کیلومترها آنطرفتر صحبت کنیم و یا این که داخلِ گوشیمان چه اتفاقی میافتد که میتوانیم به ردوبدل دیتا بپردازیم، فکر نخواهیم کرد.
به منظور درک بهتر این موضوع، فرض کنیم کلاسی داریم به نام User
که حاوی متدی است تحت عنوان ()changePassword
که از جنس private
است؛ به عبارتی، فقط و فقط خودِ این کلاس به چنین متدی دسترسی داشته و دیگر بخشهای وب اپلیکیشن از اِعمال هر گونه دخلوتصرف در آن منع شدهاند زیرا همانطور که پیش از این گفتیم، گویی این آبجکت داخل یک کپسول قرار دارد. در عین حال، یک سری متد دیگر نیز برای این کلاس در نظر گرفته شده که عبارتند از ()showName
و ()showDetails
که از جنس public
میباشند؛ به عبارت بهتر، دیگر بخشهای نرمافزار اجازهٔ دسترسی به آنها و بالتبع استفاده از آنها را دارند.
به طور کلی، Encapsulation باعث میگردد تا توسعهدهنده به شکل بهتری بتواند دست به مدیریت الگوریتمهای به کار رفته داخل تکتک کلاسهای نرمافزار خود بزند چرا که به خوبی خواهد دانست که کدام بخشهای کلاسها در معرض دید سایر بخشهای نرمافزار بوده و بالتبع میتوانند دستخوش تغییر شوند و کدام بخشها منحصر به خودِ کلاس هستند. اساساً هر چه اجازهٔ دخلوتصرف در کلاسها توسط سایر بخشهای سورسکد کمتر باشد، مدیریت و نگهداری نرمافزار سادهتر خواهد بود و این همان مزیتی است که Encapsulation در اختیارمان میگذارد (در اکثر زبانهای برنامهنویسی شیئگرا همچون جاوا و پیاچپی، این خصیصه توسط اصطلاحاً یک سری Access Modifier من جمله private
و public
عملی میگردد که در آموزشهای آتی آنها را مورد بررسی قرار خواهیم داد.)
علاوه بر مفاهیم چهارگانهٔ فوق، در شیئگرایی یک سری قوانین دیگر نیز داریم که در نهایت منجر بدین خواهند شد تا نرمافزارمان در طول زمان راحتتر مدیریت گردد که در ادامه برخی از مهمترین آنها را برخواهیم شمرد.
آشنایی با مفهوم Loose Coupling
به طور کلی، اصطلاح Coupling به ارتباط مابین اجزای مختلف یک نرمافزار اشاره دارد و در شیئگرایی توصیه میشود که کلاسها میباید تا حد ممکن ارتباط حداقلی با دیگر کلاسها داشته باشند که چنین چیزی تحت عنوان Loose Coupling شناخته میشود. اساساً اگر چنین استانداردی را در توسعهٔ نرمافزار رعایت کنیم، در نهایت سورسکدی خواهیم داشت که ماژولار بوده و به سادگی میتوان ماژولها یا کامپوننتهای مختلف آن را جدا کرده و در سایر پروژهها استفاده نمود و نیاز به توضیح نیست که اگر ارتباطات پیچیدهای مابین اجزای نرمافزار وجود داشته باشد، استفادهٔ مجدد از ماژولهای آن سخت و گاهی هم غیرممکن خواهد شد.
آشنایی با مفهوم SoC
Separation of Concerns یا به اختصار SoC حاکی از آن است که ماژولهای مختلف یک نرمافزار میباید به گونهای توسعه پیدا کرده باشند که وظایف آنها با یکدیگر تداخل پیدا نکند و به نوعی هر کلاس یک وظیفهٔ مشخص داشته باشد و آن را هم به خوبی انجام دهد. به طور مثال، کلاسی همچون User
میباید فقط و فقط کلیهٔ کارهای مرتبط با کاربران را هندل کند و مثلاً نمیباید با کلاسی همچون Cart
که وظیفهٔ هندل کردن تَسکهای مرتبط با سبد خرید را دارا است تداخل داشته باشد.
آشنایی با مفهوم DRY
Don't Repeat Yourself یا به اختصار DRY اصلی است با محوریت این موضوع که میباید از هر گونه قطعه کد تکراری در داخل سورسکد جلوگیری به عمل آید که چنین چیزی را از طریق کلاسها و متدهای مختلف میتوان عملی ساخت تا جایی که هر موقع نیاز به ریفکتور کردن بخشی از کد داشته باشیم، صرفاً نیاز است تا یک بلوک از کد را دستخوش تغییر سازیم که همین مسئله زمان توسعهٔ نرمافزار از یکسو و همچنین کاهش وقوع باگها به دلیل خطاهای سهوی از سوی دیگر را به حداقل میرساند.
آشنایی با قوانین SOLID
SOLID مجموعهٔ دیگری از پنج اصل توسعهٔ نرمافزار با پیروی از متودولوژی شیئگرا میباشد که پیروی از آنها این اطمینان را ایجاد میکند که معماری اپلیکیشنمان قابلدرک، انعطافپذیر و همچنین قابلنگاهداری باشد که امروزه در توسعهٔ نرمافزارهای اینترپرایز مورد استفاده قرار میگیرد که برای آشنایی بیشتر با این قوانین پنجگانه، میتوانید به دوره آموزش قوانین SOLID در سکان آکادمی مراجعه نمایید.
آشنایی با مفهوم Design Pattern
Design Pattern در پروسهٔ توسعهٔ نرمافزار طبق اصول اواوپی به راهکاری تضمینی برای حل مسائلی تکراری اشاره میکند که پیش از این توسط دولوپرهای حرفهای ابداع گردیده و آزمایش خود را پس دادهاند و این در حالی است که سایر دولوپرها با خیال راحت میتوانند با پیروی از آنها اپلیکیشنهایی توسعه دهند که انعطافپذیر، قابلتوسعه و نگهداری، ساختاریافته و همچنین اصولی باشند که در همین راستا و جهت آشنایی بیشتر با این مقوله، میتوانید به دوره آموزش الگوهای طراحی در سکان آکادمی مراجعه نمایید.