الگوی State
این الگو به شما در موارد زیر کمک میکند
- Simplify کردن کد
- امکان جدا کردن و مشخص نمودن مسولیتها
- افزایش Maintainability
این الگو به شی اجازه میدهد تا رفتار متدها را بر اساس وضعیت شی تغییر دهد.
مثال: سیستم کنترل نسخه نرم افزار و زیر سیستم Tracking Work Item
در این نرم افزار، بر اساس وضعیت اشیا در هر لحظه رفتار اشیا نیز تغییر میکند. حتی برخی متدها قابل اجرا نخواهند بود. برخی متدها نیز رفتارشان براساس وضعیت تغییر خواهد کرد.
این رفتار حتی براساس template ی که به سیستم اعمال میشه میتونه تغییر کنه.
پیاده سازی نرم افزار روش ساده یا Naiive: داخل هر متد یا عملیات یک بلاک سوییچ و case وجود داره که بر اساس وضعیت شی رفتار متفاوتی رو انجام میده.
مثلا: وقتی متد Delete صدا زده میشه و شی در وضعیت Proposed است عمل حذف انجام میشه. اگه شی در وضعیت Active باشه پیغامی نمایش داده میشه و اجازه حذف نمیده و به همین ترتیب...
هر کدام از اکشنها بر اساس وضعیت جاری آیتم کاری رفتاری رو نشون میده یا وضعیت آیتم رو تغییر میده. مثلا اگه آیتم کاری در وضعیت Proposed باشه و شما متد Open را فراخوانی کنید، وضعیت شی میشه Proposed و به همین ترتیب.
بقیه اکشنها هم به همین نحو عمل میکنند.
به زودی نگهداری همچنین کدی بسیار سخت میشه. چرا که مدام کلاس WorkItem ما با اومدن وضعیت جدید رشد میکنه و تمامی اکشنها تغییر میکنن. یعنی وضعیت جدید را باید در تمامی متدها پیاده سازی کنیم.
* State ها هارد کد شده اند با تعریف وضعیت جدید باید وارد هر متد شده و یک Case جدید به Switch بیفزاییم.سایر مشکلات:
Mixed Concerns: همه اکشنهای یک State خاص در کد پخش و پلا شده اند. ممکنه با تغییر یک اکشن برای یک State وضعیت برای بقیه وضعیتها نیز عوض بشه. یعنی مجبوریم دوباره تست کنیم.
Difficult Unit Test: همه اینا باعث سخت شدن Unit Test میشن.
هدف از الگوی State:
- با تغییر وضعیت شی رفتار شی هم عوض بشه. یعنی با تغییر State شی تمامی اکشنهای این تغییر وضعیت نیز اجرا و تغییر کنند.
- کپسوله کردن لاجیک هر State در یک شی مختص. باید بتونم هر وضعیت شی مثلا Proposed را با تمام انواع اکشنهایش مجزا بتونم تست کنم بدون اینکه تداخلی با اکشنهای بقیه وضعیتها پیش بیاد.
- میخوام امکان تشخیص داینامیک وضعیتهای جدید افزوده شده رو داشته باشم. در روش ساده همه چی هارد کد میشد. در حالیکه میخوام نرم افزار اونا رو داخل برنامه integrate کنه.
- امکان unit testing رو میخوام ساده تر کنم.
در این الگو یک سری اشیا ایجاد میکنیم که اکشنهای هر کدام از State ها را نمایش میدهد.
کد رو به 3 قسمت تقسیم میکنیم.
- Original Item که همان WorkItem است
- اینترفیس اکشنها که Icommands است.
- و پیاده سازی شده این اینترفیس برای هر کدام از وضعیتهای مختلف. همچنین یک BaseState نیز داریم که هر کدام از وضعیتها فرزند آن هستند که استفاده اشتراکی از برخی متدها را برای ما فراهم میکند.
- حتی WorkItem نیز اینترفیس Icommand رو پیاده سازی میکنه.
- در حقیقت اکشنها آنهایی هستند که در تمام وضعیتها استفاده میشوند.
- در اینجا اکشن خاصی برای تبدیل وضعیتها به یکدیگر مثلا Proposed به Active نداریم. از فراخوانی متد SetState برای این تبدیلات استفاده میکنیم.
- کلاس WorkItem یک وضعیت را new میکنه و وقتی وضعیت تغییر میکنه
اولین قدم تعریف قراردادی است که همه ی اکشنهای مورد نیاز وضعیتها را تعریف میکند.
Implementation:
برنامه با ایجاد یک Data Coection شروع میشه.
برنامه دستور ورودی را parse میکنه
- اکشنهای WorkItem به اینترفیس ICommand کاهش یافته است.
همچنین دوتا instance variables داریم یکی stateCommands و دیگری state
Workitem متدهای ICommands رو پیاده سازی میکنه. و داخل متدها در حقیقت خود متدهای stateCommand رو فراخوانی میکنه.
حالا زمانی که یک State تغییر میکنه. در اینصورت وضعیت stateCommand نیازه که عوض بشه. اینکار با پیاده سازی پروپرتی State انجام میشه.
بر اساس وضعیتی که برای شی workItem اومده تعیین میشه که چه نوعی از state باید new بشه.
دیگه هیچ سوییچ کیس داخل متدها نداریم.
درسته که هنوز State ها از WorkItem جدا نشده اند ولی مدیریت آنها بسیار ساده شده است.
بررسی وضعیتها:
خود State ها به کلاسهای مجزا از همی شکسته شده اند
هم WorkItem و هم State ها از Icommand پیاده شده اند پس دارای نقاط اشتراکی فراوانی خواهند بود.
مثلا در سازنده هر State ارجاع به کلاس WorkItem به عنوان مالک داریم.
مثلا در متد Print ما workItem را به متد پاس میدیم که خودش در base class است.
در رابطه با متد Delete ما نیاز داریم که بدانیم در هر وضعیت ما میتوانیم آیتم را حذف کنیم یا نه؟ یعنی هر وضعیت یک فلگ آری یا خیر برمیگردونه
سپس WorkItem براساس پاسخی که از State اومده میتونه تصمیم بگیره اونو حذف کنه یا نه
مزایای State Pattern:
- قبل از این الگو WorkItem وظایف مختلفی از جمله مدیریت وضعیتها و دیتا و
رفتارهای متد را داشت. ولی الان تنها وظیفه ایجاد مدیریت دیتا رو داره.
- رفتار متدها خاص هر وظیفه ای شده است.
- هر کدام از وضعیتها کاملا مشخص شدهاند.
- خود شی state میتونه Reuse بشه.
- میتونید از الگوی FlyWeightبهره بگیرید تا تنها تعداد محدودی از هر وضعیت ایجاد بشه.
- خیلی وقتا State Object تنها یک نمایش داره و با الگوی Singleton میتونی تنها یه دونه از اون ایجادکنی.
- تست ساده تر
- برنامه سادهتر
- نگهداری کاراتر
چند مثال کاربردی:
- TCP Coection Protocols : بر اساس وضعیت Cocetion هر کدام از متدهای open close acknowledge میتونن رفتارهای متفاوتی داشته باشن.
- Mouse Pointer : کرسر و رفتار متفاوت کرسر بر اساس موقعیتهایش. مثلا روی لینکها یه شکلی میشه روی عکسها یه شکل دیگه. بر اساس هر تغییر آپشنها تغییر میکنند
- وقتی متدهای یک شی نیازه مه در زمان اجرا رفتارشون عوض شه.
- اگه کلی سوییچ کیس بنویسی که بر اساس فیلدهای شی باشن.
برچسب:
نویسنده: امیر کریمی