در بین پزشکان، مهندسان عمران و مهندسان کامپیوتر همیشه بحث بوده که حرفه کدامیک ریشه قدیمی تری نسبت به دیگران دارد. پزشکان معتقدند بر اساس کتاب مقدس خدا حوا را از بطن آدم ساخته است پس این کاملا مشخص میکند که جراحی قدیمی ترین حرفه ایست که به آن اشاره شده است. ولی مهندسان عمران مدعی اند قبل از آدم و حوا خداوند دنیا و بهشت را از نظم دادن به یک حرج و مرج مطلق پدید آورده است بنابراین ساخت و ساز را میتوانیم به عنوان اولین حرفه در هستی در نظر بگیریم. اما در این بین مهندسان کامپیوتر به صندلی خودشون تکیه میزنند و با لبخندی تمسخر آمیز و با اعتماد به نفس میگویند: هیچ با خود فکر کرده اید اون هرج و مرج را چه کسی بوجود آورده بود؟؟
عموما پیچیدگی باعث هرج و مرج میشود. اما با نگاهی به طبیعت میتوانیم ببینیم که بسیاری از سیستم های که در طبیعت با موفقیت مشغول به کار هستند دارای پیچیدگی قابل تاملی هستند.
باید بپذیریم که پیچیدگی محصول نرم افزاری ویژگی ذاتی آن است و نه یک اتفاق تصادفی چرا که عموما مشتری نمیداند چه چیزی میخواهد، چیزی که میخواهد را نمیتواند واضح بیان کند و توسعه دهنده هم برداشت خود را از خواسته های مشتری دارد.
از آنجایی که تغییر اصل ثابت برای هر پروژه نرم افزاری است. طراحی ناموفق در مقابل تغییر پیش بینی نشده واکنش نشان میدهد. «بله من میتوانم این امکانات را اضافه کنم اما همه چیز از کار می افتد!!» «نه من نمیتوانم این امکانات را اضافه کنم چون این سیستم برای این کار طراحی نشده!!» «من میتوانم این امکان را به سیستم اضافه کنم اما این دقیقا اون چیزی نیست که شما میخواهید و در نهایت پشیمان خواهید شد!!»
اتفاقات دنیا به صورت رویه ای است. یعنی زمان به سمت جلو حرکت میکند و اتفاقات یکی پس از دیگری رخ میدهند. احتمالا با شروع صبح از خواب بیدار میشوید. دندان های خود را مسواک میزنید و صبحانه خورده و از خانه برای کار خارج میشوید.
دنیا همچنین شی گرا نیز هست. اشیایی که با آنها سر و کار داریم میتوانند رابطه یک سگ و شما باشد که هر کدام با رفتار خود تعریف میشوید. ممکن است بعضی اتفاقات قابل پیش بینی بین شما نتیجه ای دور از انتظار داشته باشد. ممکن است براساس خصوصیات ظاهری و رفتار سگی ترس را در شما برانگیزد در حالیکه سگی دیگر با شما این کار را نکند. در واقع در آنالیز شی گرا بجای چگونه بر چه تاکید داریم. هر شی چه میداند و چه کاری انجام میدهد؟
و اما کدام روش برای طراحی سیستم درست است؟ در واقع هر دو دید برای تجزیه و سپس غلبه (divide and conquer) بر تکه های کوچک شده مسئله لازم است. با توجه به اینکه آنالیز و طراحی شی گرا به داشتن دید کاملی از کل سیستم در همان ابتدای طراحی نیازی ندارد و بعلاوه توسعه کاملا منطبق با طراحی انجام شده پیش خواهد، همچنین تجربه به ما میگوید که بهتر است ابتدا با دید شی گرا وارد مرحله طراحی شویم زیرا به ما کمک میکند تا پیچیدگی های سیستم را به قسمت های کوچک سازماندهی کنیم.
در طراحی به روش شی گرا بخش های مختلف با برقراری ارتباط با هم رفتار سیستم را شکل میدهند. این ارتباط از طریق یک قرارداد انجام میشود طوریکه پیام وسیله انتقال بین آنها است. این موضوع مستلزم آن است که فرستنده پیام، اطلاعاتی از دریافت کننده داشته باشد. این اطلاع داشتن منجر به وابستگی بین اشیا میشود.حل مشکل پیچیدگی در طراحی شی گرا مدیریت این وابستگی ها است.
شی چیست؟ و چه نیست؟
در محیط اطرفمان هر چیزی که قابل مشاهده یا لمس باشد و از لحاظ فکری قابل فهم باشد را شی در نظر میگیریم ولی در دنیای شی گرایی کمی بیش از این است. هر چیزی که دارای وضعیت مشخص، رفتار قابل تعریف (چگونگی کنش و واکنش و تغییر وضعیت) و شناسه واحد یا یونیک (که آنرا از دیگر اشیا متمایز کند) باشد را شی در نظر میگیریم. و همچنین مجموعه ای از اشیا که ساختار مشترک، رفتار مشترک و معنای مشترک دارند یک کلاس را تشکیل میدهند.
برای طراحی شی گرا باید با ابتدا آنالیز مفهومی از سیستم داشته باشیم. مفاهیم مرتبط با شی گرا به ما امکان طراحی دقیق تری را میدهد:
Abstraction یا انتزاع:
یک گیاه داری سه بخش اصلی است (ریشه، ساقه، برگ) که وظیفه و عملکرد هر کدام متفاوت از هم هستند در حالیکه همه آتها از سلول های مشابه ساخته شده است. هر بخش با رابط خود با بخش های دیگر در ارتباط است. ریشه وظیفه جذب آب و مواد معدنی از خاک را برعهده دارد. ساقه وظیفه انتقال مواد معدنی را از ریشه دارد. برگ ها با استفاده مواد معدنی دریافت کرده وظیفه تولید از طریق فتوسنتز را بعهده دارند. محدوده وظایف بصورت کاملا واضحی مشخص است در حالیکه بطور مثال ما چیزی از فرایند شیمیایی تبدیل مواد معدنی و فتوسنتز نمیبینیم.
انتزاع ویژگی های اصلی یک شی را مشخص می کند طوریکه آن را از همه انواع دیگر اشیا تشخیص دهیم و بدین ترتیب مرزهای مفهومی دقیقی را با توجه به دیدگاه بیننده از یک شی پدید می آورد.
برای رشد هر گیاه نیاز به تغذیه با مواد معدنی و آب و نور و دمای مناسب داریم. فرض کنید میخواهیم یک گلخانه اتوماتیک داشته باشیم که بدون دخالت انسانی و تنها با استفاده از چند سنسور دما، گرما، مواد معدنی را اندازه بگیرد و یک طرح را با استفاده داده های بدست آمده برای بهبود شرایط رشد اعمال کند. همه چیزی که شما میبینید چند سنسور است که در جاهای مختلف گلخانه نصب میشود که از طریق چند انتزاع دیگر میزان دما و نور را تغییر میدهد.
انتزاع از همکاری اشیا (object) با همدیگر برای انجام یک رفتار بوجود می آید.
Encapsulation یا کپسوله سازی:
کپسوله سازی و انتزاع دو مفهوم بسیار به هم تنیده اند. نباید هیچ بخشی از یک سیستم پیچیده به اجزای درونی بخش های دیگر وابسته باشد. در حالیکه انتزاع بر رفتار قابل مشاهده هر شی تاکید دارد، کپسوله سازی بر نحوه پیاده سازی که منجر به رفتار میشود تمرکز میکند.در واقع انتزاع به ما کمک میکند تا کاری که انجام میدهیم برایمان قابل درک باشد ولی کپسوله سازی اجازه میدهد که برنامه با کمترین تلاش بتواند تغییر کند.
در واقع هر شی به اینکه چگونه کارهای مربوط به خود را انجام میدهد آگاه است اما دیگر اشیا فقط باید بدانند شی مقابل چه کارهایی را برای آنها انجام میدهد. در واقع این شی چه کاری میتواند انجام بدهد؟ و این شی چه میداند؟ اما اینکه این شی چگونه انجامش میدهد؟ باید نسبت به سایر اشیا مخفی بماند.
فرض کنید میخواهید تکنولوژی دقیق تری را برای اینکه بخاری ها در گلخانه اتوماتیک دمای مناسب را ایجاد کنند خریداری کنید. برای اینکار باید وسیله ارتباطی که مقدار دمای تشخیص داده شده توسط سنسور را به بخاری ها اطلاع میدهد ارتقاع یابد. برای این کار نباید مجبور باشید سنسورها را عوض کنید یا تغییری در آنها بوجود آورید.
Modularity یا پیمانه ای:
سیستم باید توسط اجزای جدا از هم که قابلیت ارتباط با همدیگر را دارند کار کند. در مثال گلخانه فرض کنید میخواهید کنترل شرایط را به خود کاربر بسپارید. احتمالا کاربر نیاز دارد برای شرایط رشد ویژه گیاهان یا گلها برنامه ای داشته تتظیم کند. در اینجا در واقع مجموعه ای از انتزاع های منطقی قبلی را برای اپراتور دسته بندی کرده ایم.
Hierarchy یا سلسله مراتب:
سلسله مراتب همان مرتب سازی انتزاع ها است.در واقع با سلسله مراتب میتوانیم بگوییم کلاس جدید همان کلاس قبلی است با چیزهایی بیشتر. سلسه مراتب به دو روش قابل شناسایی است:
Is-a : ارث بری بعنوان ارتباط بین کلاس ها تعریف میشود به اینصورت که یک کلاس ساختار یا رفتار خود را با یک یا چند کلاس دیگر به اشتراک میگذارد.
در مثال گلخانه ما میتوانیم پلان های متفاوتی برای رشد گیاه یا رشد گلها داشته باشیم ولی باید هر دوی آنها یک نوع از پلان رشد هستنند.
Has-a : این نوع از سلسه مراتب را تجمع یا (Aggregation) می نامیم. در واقع ما یک انتزاع به نام گلخانه داریم که پلان رشد یا گیاهان، هر کدام بخشی از این گلخانه هستند.
Polymorphism یا چندریختی:
چندریختی به دو یا بیشتر کلاس های این امکان را میدهد تا به یک پیام مشخص جواب بدهند، البته به هر کدام به روش خود. این به آن معنی است که اشیا نیاز ندارند تا بدانند چه کسی پیام را برایشان فرستاده است.
فرض کنید چند نقطه از گلخانه ابزار آبیاری کشیده شده است. نوع وسیله آبدهی بسته به نوع گیاهان آن منطقه باید متفاوت باشد. فرض کنید سنسور ها تشخیص میدهند نیاز به آبیاری است. دستور به ابزارهای آبیاری ارسال میشود ولی بعضی از آنها شروع به آبیاری قطره ای و بعضی از روش غرقابی استفاده خواهند کرد. در واقع سیستم نسبت به یک پیام جوابهای متفاوتی اعمال کرده است.
Single Responsibility یا مسئولیت واحد :
باید در نظر بگیریم که هر کلاس تنها و فقط یک کار را انجام دهد (Single Responsibility). در واقع نباید در توضیح عملکرد یک کلاس از دو حرف ربط «و» و «یا» استفاده کرد. اما این به معنا نیست که اصل مسئولیت واحد به طور سفت و سخت تنها اجازه انجام یک کار بسیار محدود را به کلاس بدهد. در واقع یک کلاس باید به طور بسیار منسجم کارهایی را انجام دهد که هدف آن کلاس آنرا توجیه کند.
اینکه دقیقا بتوانید در همان نگاه اول و در ابتدای طراحی برای هر کلاس مسئولیت واحد را تشخصی دهید تقریبا غیرقابل ممکن است. شما همیشه بین دو سئوال باید تصمیم بگیرید بهتر است الان آن کلاس را بهبود بدهم؟ یا بعد آنرا بهبود خواهم داد؟
مدیریت وابستگی ها:
اگر کلاس های خود را انسان فرض کنید و به آنها در مورد وابستگی توصیه ای بدهید، باید بگویید: به چیزی وابسته باش تا کمتر از خودت تغییر کند.
۱. از نیازمندیها مشخص است که بعضی از کلاس ها احتمال بیشتری نسبت به بقیه برای تغییر دارند.
۲. کلاس های انتزاعی (abstract) نسبت که کلاس های ذاتی نیاز کمتری به تغییر پیدا میکنند.
۳. تغییر کلاسی که وابستگی های زیادی دارد دردسرساز خواهد شد.
هیچ کلاسی نباید از نام کلاسی دیگر مطلع باشد یا برای استفاده از کلاس دیگر مجبور به صدا کردن نام کلاسی که به آن وابسته است را داشته باشد.
مقادیر آرگومان (Argument) کلاسی که به آن وابستگی وجود دارد نباید به عنوان ورودی پیام به کلاس دیگر فرستاده شود. در واقع کلاسی که به آن وابستگی داریم یک نمونه از خود را میسازد و همراه پیام میفرستد.
ساختار پیامی بین دو کلاس فرستاده میشود نباید غیرقابل تغییر باشد. در واقع برای فرستادن پیام نباید مجبور به رعایت ترتیب آرگومان ها باشیم.
فرض کنید در کلاسی به نام سفارش میخواهیم قیمت را محاسبه کنیم:
قیمت تمام شده سفارش = تعداد سفارش * قیمت محصول
همانطور که میبینید کلاس سفارش برای محاسبه نیاز به محصول دارد که این وابستگی را میتوانیم بصورت صدا کردن یک متد از کلاس محصول که محاسبه قیمت محصول را انجام میدهد و سپس تزریق آن نمونه (instance) از طریق ورودی پیام به سفارش انجام بدهیم تا سفارش بتواند قیمت را محاسبه کند.
کلاس ذاتی و انتزاع:
انتزاع را میتوان به « مستقل از هرگونه نمونه مشخص » تعریف کرد. در شی گرایی میتوانیم آنرا «بدون هیچگونه محدودیت و وابستگی فنی خاص به کلاس دیگری» تعریف کنیم.
در ناحیه A بدلیل وجود وابستگی های شدید از انتزاع (abstraction) استفاده میکنیم. ناحیه C در مقابل این ناحیه A قرار دارد و ما در اینجا بدلیل وابستگی کم از کلاس های ذاتی استفاده میکنیم زیرا در صورت تغییر نیاز به تغییری دیگران ایجاد نمیکند.
ناحیه B آنچنان مورد نگرانی ما نخواهند بود چرا که هم تغییر کمی خواهند داشت و هم وابستگی ندارند. اما ناحیه D منطقه خطر نام دارد به این معنی که اگر هم وابستگی های یک کلاس زیاد است و هم تغییرات در آن محتمل است، زنگ هشدار در مرحله طراحی به صدا خواهد آمد زیرا تغییرات کوچک کل سیستم شما را متاثر خواهد کرد و این چیزی است که دردسر ساز خواهد بود پس سعی کنید فرایندهای مرحله طراحی را بازبینی کنید.
Interface یا رابط:
دو شکل بالا را در نظر بگیرید. در شکل سمت چپ هر شی بدون هیچ الگوی مشخصی اجازه دارد با دیگر اشیا ارتباط بگیرند ولی در شکل سمت راست فقط بعضی از اشیا میتوانند با هم ارتباط مستقیم داشته باشند که امکان انتقال پیام بین همه اشیا با یک الگوی مشخص همانند پلی قرار داد شده امکان پذیر است. Interface ها جایی که اشیا با هم تعامل میکنند قرار میگیرند.
یک شرکت دوچرخه سواری را در نظر بگیرید که تور دوچرخه سواری برگزار میکند. کار این شرکت این است که با توجه به موجود بودن دوچرخه ای که مشتری انتخاب میکند و انتخاب مسیر در زمانی مشخص به او پیشنهاد تور دوچرخه سواری بدهد.
دو کلاس Trip و مشتری را در نظر بگیرید. مشتری به کلاس Trip درخواست میدهد و این کلاس براساس شرایط سختی، روز و دوچرخه یک تور مناسب ارائه میدهد. اولین نکته این است که کلاس Trip علاوه بر مسئولیت های خود که تشخیص میزان سختی و زمان تور است باید نسبت به موجود بودن دوچرخه نیز پاسخگو باشد که این اشتباه است.
سوال پایه ای خود را از «من میدانم این کلاس چه کاری انجام میدهد؟» را به سوال «من میدانم نیاز دارم این پیام را در سیستم داشته باشم. چه کسی باید به این پیام پاسخ بدهد؟ » تغییر دهید. شما بخاطر اشیای موجود در سیستم پیام نمیفرستد بلکه بخاطر وجود پیام ها اشیا را میسازید. در واقع مشکل مشتری نیست که به دنبال دوچرخه موجود است. مشکل اینجاست که کلاس Trip نباید به آن پاسخ دهد.
در این مرحله این مشکل را حل کردیم اما کلاس مشتری علاوه بر اینکه میداند چه میخواهد، میداند اشیا دیگر چگونه با همکاری با هم این کار را برای او انجام میدهند.
فرض کنید به نیازمندیهای پروژه اضافه میشود که برای هر تور باید دوچرخه ها از نظر مکانیکی از جمله بررسی ترمزها، باد زدن چرخ ها، زنجیر و ... بررسی شوند. با توجه به اینکه کلاس Trip نباید از چکونگی مکانیک چیزی بداند پس داریم:
کلاس مکانیک خود میداند که چه کارهایی را شامل میشود اما این مانند کتی بر تن Trip است زیرا هر بار تست Trip منجر به تست کلاس مکانیک خواهد شد.
اما یک مرحله بالاتر:
حالا کلاس Trip با استفاده از Interface میتواند کاملا از کلاس مکانیک بی خبر باشد و نه تنها از چگونگی انجام کار مطلع نشود حتی از وجود همچین کلاسی نیز مطلع نباشد.
در واقع در تلاش اول، کلاس Trip به مکانیک میگفت «من میدانم چه میخواهم و میدانم تو چه کاری انجام می دهی» ولی در دومین تلاش میگوییم: «من میدانم چه میخواهم پس به قسمتی که آنرا انجام میدهد اعتماد میکنم». این اعتماد کور کورانه اساس روش شی گرا است.