GRASP چیست ، چه دستاوردهایی برای ما دارد و چه مشکلاتی را برای ما حل می کند ؟

یکی از مباحثی که ما از ابتدای شروع به برنامه نویسی بسیار از آن می شنویم و مطالعه کنیم برنامه نویسی شیء گرا یا همان Object Oriented Programming است. اینکه میگوییم زبان C# یا C++ برای برنامه نویسی شیء گرا هستند الزاماً به این معنا نیست که ما شیء گرا کد میزنیم.
شیء گرایی به سه بخش تقسیم می شود:
1- تحلیل و آنالیز شیء گرا (فهمیدن مساله)
2- طراحی و مدل سازی شیء گرا (راه حل مسئله)
3- برنامه نویسی شیء گرا (ساختن راه حل)
در این مقاله ما فقط در مورد تحلیل و آنالیز ، طراحی و مدل سازی شیء گرا صحبت می کنیم و اصلا در مورد کد نویسی در این بخش صحبتی نداریم.
دو اصل از Object Oriented Programming مباحث SOLID و GRASP هستند.
این دو اصل برای ما Single Responsibilityرا به همراه دارند و اگر Single Responsibleرا بصورت کلمه ای معنی کنیم می توان گفت کلاس یک مسئولیت داشته باشد. اما بصورت کامل تر این گونه بیان می شود که یک کلاس تنها یک دلیل برای تغییر داشته باشد و محور تغییرات برای ما اهمیت پبدا می کند. اینکه این تغییر چقدر می تواند تغییرات در سایر اجزای نرم افزار ایجاد کند.
در این مقاله ما در خصوص General Responsibility Software Assignment Pattern صحبت می کنیم.
اجزای GRASP شامل موارد زیر هستند :
اصل اول : Expert
این بخش تاکید برای روی تخصص یک کلاس بر روی وظیفه ای است که انجام می دهد. این تخصص در حوزه Data می باشد. برای مثال کلاسی داریم که وظیفه برقراری ارتباط با دیتابیس را دارد. ما نمی توانیم رفتارهایی مانند محاسبه مانده حساب یک مشتری را در آن قرار بدهیم. آن کلاس فقط وظیفه برقراری ارتباط با دیتابیس و ارسال query برای دریافت نتیجه می باشد و رفتار نامربوطی نباید در آن گذاشته شود.
اصل دوم : Creator
این بخش تاکید بر این است که اگر قرار است یک کلاس ساخته شود یک کلاس مسئول ساخته شدن آن باشد. کلاسی که به ورودی های آن برای ساخته شدن آگاه باشد. برای مثال در EntityFramework ملاحظه کردید که یک کلاس DbContrext داریم که کلاس های متناظر ما در دیتابیس در آن وجود دارد. DbContext در اینجا نقش Creator را دارد. کلاسی برای ما میسازد که مشخصات خاصی دارد و می داند چگونه با دیتابیس کار کند. برای مثال دیگر می توان از Factory Pattern نام برد که میداند چگونه یک کلاس را ایجاد و به درخواست کننده بدهد.
اصل سوم : Controller
این بخش تاکید بر این دارد که مواردی که در UI هستند نباید بصورت مستقیم Business ما را انجام دهند. طبق اصل expert اصلا این کلاس وظیفه تعامل به UI را دارد و نباید business که از آن اطلاع ندارد را اجرا کند.آیآیند
اصل چهارم : Pure Fabrication
این بخش می گوید اگر یک مشئولیتی بین دو کلاس وجود دارد ولی مسئولیت مشخص نیست مشخص نیست دقیقا به کدام کلاس مربوط است یا در هردو معنی پیدا می کند یک کلاس جدید خلق می کنیم و مسئولیت را با آن می دهیم.
یک مثال میزنم. نرم افزاری داریم برای محاسبه شارژ یک آپارتمان ، کلاس واحد آپارتمان وظیفه نگه داری تعداد افراد ، متراژ ، طبقه و ... را دارد. کلاس قبض هم وظیفه نگه داری مواردی چون تاریخ و دوره ، مبلغ و ... را دارد. حالا میخواهیم محاسبه هزینه قبض برای هر واحد را انجام بدهیم. اینکه نکته را در نظر بگیریم که قبض گاز به مبلغ مساوی بین واحد ها تقسیم می شود ولی قبض گاز به تعداد نفرات هر واحد بستگی دارد.
در اینجا سوال پیش می آید که پیاده سازی نحوه محاسبه در قبض اگر باشد پس باید تعداد نفرات واحد را بداند. اگر محاسبه در کلاس واحد آپارتمان باشد از نحوه محاسبه قبض خبر ندارد. پس یک کلاس اینجا به وجود می آید به نام محاسبه گر که وظیفه دریافت اطلاعات قبض و اطلاعات واحد و محاسبه و اعلام سهم آن واحد را بر عهده می گیرد.
اصل پنجم : Indirection
قرار دادن یک شیء بین دو شیء دیگر برای از بین بردن وابستگی آنها. در نظر بگیرید شما نرم افزاری دارید که میخواهد در جایی از کار یک SMS ارسال کند (کاری از یک سیستم دیگر را انجام دهد) در اینجا شما ممکن است به این فکر کنید که شاید در آینده از provider دیگری استفاده کنید. در نتیجه پیاده سازی SMS را در همان کلاسی که هستید انجام نمی دهید. در یک کلاس واسط انجام می دهید که اگر provider عوض شد متد ها و کلاس های دیگر تغییر نکند و فقط کلاسی که وظیفه ارسال SMS را دارد تغییر خواهد کرد.
در SOLID می توان Dependency Inversion را مثال زد و در الگوی ها Adapter Pattern این موضوع را در نظر دارد.
اصل ششم : Polymorphism
چند ریخی که بسیار در OOP از آن شنیده ایم. یعنی تشخیص اینکه حین اجرا از کدام کلاس استفاده کند و چگونه پیاده سازی شود.
برای مثال در نظر بگیرید یک کلاس داریم که مساحت را حساب می کند. برای اشکال مختلف یک محاسبه که از بیرون آن را صدا می زنیم و این گونه در نظر میگیریم که ما یک کلاس داریم که مسئولیت محاسبه مساحت را داریم. اما این درست است ؟
حال اینگونه در نظر بگیریم. یک کلاس اصلی داریم مثلا به نام Shape که متدی تحت عنوان محاسبه مصاحت دارد. هر shape جدیدی که داریم یک نمونه از آن پیاده سازی می کنیم. در نتیجه اگر یک شکل جدید به نرم افزار اضافه کنیم یک کلاس جدید اضافه میکنیم که پیاده سازی محاسبه مصاحت شکل در آن است. اگر هم مشکل یا بهینه سازی در شکل های قبلی داشتیم فقط همان کلاس تغییر می کند و نه کلاس های دیگر و نه کلاس abstract.
اصل هفتم : Protected variations
مخفی کردن متغیر ها از تغییرات. راهکارهای زیادی برای این بخش وجود دارد. Encapsulation ، Polymorphism و interface ها راهکارهایی هستند که این امر را پوشش می دهند.
و همچنین بحث OCP در SOILD نیز این موضوع را در نظر دارد.
اصل هشتم و نهم : Low coupling و High cohesion
اما میتوان گفت یکی از اهداف اصلی در کد نویسی شاید همین دو مورد آخر یعنی مشارکت بالا و وابستگی کم باشد. در این جاست که خیلی مباحث modular بودن و سرویس های بین نرم افزار ها شکل میگیرند.
انتخاب اینکه کدام بیشتر و کدام کمتر باشد یک تشخیص بین منافع و ضررهای هرکدام است. اگر وابستگی بسیار پایین باشد شاید همکاری بالا را از دست بدهیم و اگر همکاری را بالا ببریم وابستگی را بیش از حد کرده ایم.
این موضوع در سیستم هایی با پیچیدگی بالای business بسیار خود را نشان می دهد، یک طراحی غلط باعث می شود سیستم دچار high coupling و low cohesion شود.
اما رعایت تمامی 7 اصول قبلی به نوعی ما را به موارد 8 و 9 می رساند اما نه به صورت کامل، حال چه جیزی می تواند این دو اصل را تضمین کند ؟
همانگونه که گفته شد بحث trade off بین این دو اصل برقرار است ، اینکه چقدر وظایف بین کلاس ها تقسیم شود. ارتباط بین کلاس ها به چه صورت باشد. مثلا اگر کلاس هایی باشند که به عنوان نماینده ای از کلاس های دیگر در رابطه ها شرکت کنند ، تغییرات تاثیر کمتری بر سایر کلاس های سیستم دارد. مثلا در نظر بگیرید ثبت یا تغییر شماره تلفن یک مشتری در جاهای مختلفی از سیستم استفاده شده است. حال اگر متد ورودی آن تغییری داشته باشد در همه جاهای سیستم که از آن استفاده می کنند نشت پیدا می کند ، اما اگر این تغییر توسط کلاس مشتری انجام شده باشد ، تغییر فقط در کلاس مشتری است و نه در همه استفاده کنند های آن.
این ادبیات را در Domain Driven Design می توان به وضوح دید. جایی که ما از Aggregate Rootها صحبت می کنیم.
https://dzone.com/articles/the-relationship-between-modularity-and-polymorphi
https://dzone.com/articles/solid-grasp-and-other-basic-principles-of-object-o