الگوی استراتژی چیست؟

چیزی که ما تا الان شنیدم در مورد شی گرایی اینه که خیلی خوبه و همیشه کار راه انداز هستش, ولی واقعیت اینه که همیشه این طوری نیست که بخواد کار راه انداز باشه و باعث کاهش حجم کد بشه اتفاقا بعضی جاها ممکنه استفاده از ویژگی های شی گرایی باعث افزایش حجم کد و سخت تر شدن نگه داری کد بشهفرض رو میزاریم که به ما گفتن قراره یه سیستم نرم افزاری جدید طراحی کنیم( اینجا سعی میکنم اول با یه بازی شروع کنیم تا بعدا اگه فرصت شد توی دنیای واقعی این مسائل رو مطرح کنیم) وقتی میریم و در مورد این سیستم صحبت میکنیم متوجه میشیم که قراره یه بازی خیلی ساده طراحی کنیمحالا این بازی چی هستش ؟ قرار شده یه بازی جنگ ستارگان طراحی کنیم(حالا چرا این بازی؟ آبجکت هایی که داریم داخل بازی به مرور هم پیچیده تر میشن هم سلاح های جدید به بازی اضافه میشه همون چیزی که باعث میشه ارث بری عملا باعث افزونگی کد ها و سخت تر شدن نگه داری برنامه بشه)خب توی این بازی ما نیاز به یه سری اسلحه این بازی داریم که ما اسمشو میزاریم Weapon, چیزی که تا اینجا مشخص هستش اینه که اولین متدی که توی این کلاس لازم داریم متدی برای شلیک هستشالان ما خیلی راحت میتوینم کلاس های مختلف اسلحه خودمون رو داشته باشیم و از این کلاس ارث بری کنیم, ولی نه صبر کنید, اگه ما یه سبک اسلحه جدید بخوایم به سیستم اضافه کنیم که یه متد دیگه لازم داشته باشه باید چیکار کنیم؟ آیا باید به این کلاس پایه اضافه کنیم یا باید یه کلاس جدید تعریف کنیم و از اون ارث بری کنیم؟برای ساده تر شدن درک این مسئله در نظر بگیریم که اسلحه های کلاسیکی که میخوایم استفاده کنیم تنها نیاز به شلیک دارن ولی اسلحه های مدرن کلاس 1 نیاز به Init شدن دارن, بعد از مدتی کلاس 2 هم به این دسته اضافه میشن که به غیر از Init شدن نیاز دارن تا متد دیگه ای به اسم Targeting بهشون اضافه بشه که وظیفش هدف گیری خودکار هستش, چه اتفاقی میفته اگه کلاس اسلحه های لیزری به این دسته اضافه بشه که به غیر از Shoot نیاز به یه متد دیگه به اسم Connect داشته باشه؟(اسم متد ها همه نمونه هستش خیلی روی عناوین متد ها حساسیت به خرج ندین)تا اینجای کار ما به چند حالت مختلف رسیدیم: 1) متدی که توی همه زیر کلاس هایی که از Weapon ارث بری کرده لازم هستش, 2) متد هایی که فقط تو یه سری از کلاس ها فقط لازم هستش و ممکنه توی شاخه های مختلف بعضی از کلاس ها اصلا به متد هایی که Parent اون کلاس لازم داشته نیاز پیدا نکنهاینجا همون جایی هستش که مشکل به وجود میاد و باعث میشه به فکر یه راهکار دیگه باشیم و سعی کنیم از ارث بری استفاده نکنیمخب از اونجایی که فکر میکنم ممکنه خود این مثال یه خورده فهم مسئله رو خیلی آسون نکنه از یه مثال دیگه که فقط تصویرش پایین هستش استفاده میکنم:این هم مثالی از یک بازی جنگی که در اون بعضی متد ها توی بعضی زیر کلاس ها اضافی هستشتوی مثال بالا فرض کنین که فقط Ryu توی این بازی قادر هستش که پرش کنه و عملا این متد برای Ken و ChunLi اضافه هستش, یا ChunLi و Ryu فقط نیاز به متد Kick دارن, خب الان یه مشکل دیگه ای که بهش برمیخوریم این هستش که ضرباتی که قراره ChunLi بزنه باعث انتقال برق به بدن حریف بشه در حالی که ضربات Ryu ساده هستشاگه کمی به این دو تا مثال بیشتر دقت کنید متوجه میشن که مشکل از کجا شروع میشه, اگه بخوایم توی یه جمله خلاصه کنیم:اگه توی ساختار ارث بری به متد هایی برسیم که فقط توی یه درصد خاصی از کلاس ها به اون ها نیاز داشته باشیم این مشکل بروز میکنه.بنابراین یه بار دیگه کد هاتون رو نگاه کنین و اگه جایی دیدین که مجبور شدین متدی رو پیاده سازی بکنین که بر فرض مثال در 40 درصد کلاس ها استفاده بشه و یا به متدی رسیدین که مجبور شدین توی چند کلاس مختلف کپی پیست کنین ادامه این آموزش رو بخونین چون میخوایم این مشکل رو حل کنیم.برای حل این مشکل یک روش کلی مطرح شد با اسم Strategy Patternکلید گم شده این مسئله مفهومی هستش به اسم Composition, در مواردی که نیاز داشته باشیم تا یک سری کد رو فقط توی کلاس های مشخصی استفاده بکنیم به جای ارث بری وظیفه اجرای کد های مورد نظر رو به استراتژی میسپاریم, قبل از این که بخوام توضیحات اضافه تری بدم نمودار مربوط به این الگو رو نگاه کنید:تا شده سعی کردم از نمودار هایی استفاده کنه که واضح باشهبرای رفع این مشکل ما اومدیم و رفتار هایی که میدونستیم باعث بروز این مشکل میشند رو جدا کردیم و برای اونها اینترفیس های مربوطه رو طراحی کردیم و یک آجکت از اینترفیس رو توی کلاس پایه قرار دادیم, در مرحله بعدی رفتارهایی که نیاز داشتیم رو توی کلاس های جدیدی طراحی کردیم که قراره بعدا به کلاسی که نیاز به این رفتار داره تزریق کنیم, از اونجایی که قراره استراتژی به صورت Interface و یا Abstract class پیاده سازی بشه ما هر رفتاری که نیاز داشته باشیم میتونیم به کلاس های استفاده کنه تخصیص بدیم تا در زمان اجرا بسته به شرایط رفتار مشخصی اجرا بشه.صادقانه بخوایم برخورد کنیم این روش هم مشکلات خاص خودش رو داره:همه استراتژی ها باید مشخص شده باشند از اونجایی که استراتژی به صورت interface و یا abstract هستش احتمال این که برنامه نویسی فراموش کرده باشه متد های خودش رو پیاده سازی کنه و بروز مشکل بشه وجود داردو اما مهم ترین مشکل این هستش که در زمان اجرا حداقل باید دو آبجکت ایجاد کنیم در حالی که اگه از استراتژی استفاده نکنیم فقط نیاز به یه آبجکت داریماولی مشکلات رو گفتیم الان بریم ببینیم چه مزایایی رو در اختیار ما قرار میده:از اونجایی که الگوری استراتژی به صورت interface هستش میتونیم چندین رفتار جداگانه برای برنامه تعریف کنیم و در زمان اجرا به کلاسمون بگیم رفتارهای مختلف توی شرایط مختلف از خودش نشون بدهمیتونیم رفتارهای جدید رو بعدا به سیستم اضافه کنیم بدون این که نیاز باشه تغیری توی هیچ کدوم از کلاس های قبلی بدیممیتونیم الگوریتم های که توی یه خانواده قرار میگیرین رو با ارث بری پیاده سازی کنیم و توی موقعیت های مختلف استفاده کنیم(این رو باید با مثال عملی بعدا توضیح بدم)چون رفتار ها توی کلاس هایی غیر از Context قرار داده تغییر در اونها نیازی به تغییر در Context نداره(فرض کنیم که SaveBehaviour داریم که تا الان تصاویر رو توی FileStream ذخیره میکرد و میخوایم تصاویر به جای ذخیره شدن در فایل در دیتابیس ذخیره بشه, در این صورت بدو نیاز به تغییر کلاس اصلی محل ذخیره سازی تغییر میکنه)و ...چون مطلب طولانی شد فقط برای تموم کردن این مطلب یه تصویر از یه مسئله واقعی که میتونه به کمک این الگو پیاده سازی بشه رو قرار میدم.این فقط یک نمونه از استفاده از این الگو در دنیای واقعی هستش که میتونیم در نظرش بگیریم(مسئله محاسبه هزینه رفتن از مقصد A به مقصد B) و توی پایین چند دسته مسئله که باید از این الگو استفاده کنیم:جایی که نیاز به الگوریتم های مرتب سازی داشته باشیماعتبار سنجی داده ها, مخصوصا زمانی که قوانین اعتبار سنجی پویا و نامشخص هستشبازی هاذخیره سازی داده هاقسمت هایی که نیاز به Export دیتا وجود داشته باشهاگر خواستین میتونین فکر کنین و مسئله های دیگه ای که میتونه ازاین الگو استفاده کنه رو همین جا بنویسداگر فرصتی شد توی یک مثال عملی این الگو رو توی مقاله دیگه ای می نویسم.