هر برنامهنویس پیشاز اینکه نوشتن برنامه را آغاز کند اگر بتواند دیاگرام مسئله شامل ورودیها و حالتهای موردانتظار را مشخص کند در برنامهنویسی موفقتر خواهد بود. حتی اگر بتوان این نوع تفکر را در ساختار کدنویسی خود وارد کنیم میتوانیم بسیاریاز اشکالات برنامهنویسی خود را برطرف کنیم و برنامهای با سطح خوانایی بالا داشتهباشیم.
دراین پست قصدداریم به مفهوم ماشین حالت و کاربرد آن در برنامهنویسی بپردازیم.
یکیاز مشکلات برنامهنویسان ایجاد حلقههای بینهایت در برنامه است. برای نمونه وجود تعداد زیادی وقفه و دستوراتشرطی در برنامه ممکناست احتمال چنین خطایی را افزایشدهد. گفتنیاست حلقه بینهایت دنبالهای از دستورالعملها در یک برنامه است که به شکل نامحدودی تکرار میشود یا بهاین دلیلکه شرطی برای پایانیافتن حلقه تعیین نشده، یا اگر تعیینشده، طوریاست که آن شرایط هیچگاه اتفاق نمیافتد. حلقههای بینهایت باعث میشوند تا برنامه کل زمان در دسترس پردازنده را مصرفکند، اما معمولاً کاربر میتواند آنها را از بین ببرد. یکیاز دلایلی که میتواند باعث هنگکردن سیستم شود همین حلقههای بینهایت هستند. یکیاز روشهای معمول برای رهاییاز این خطا بهکارگیری تایمرها و یا watchdog برای ریست سیستم است اما این روش به ازدسترفتن داده یا رفتارهای ناخواستهی برنامه منجر میشود. از کاربردهای ماشین حالت میتوان جلوگیریاز اشکالات برنامهنویسی، آسانترکردن اشکالزدایی برنامه و متوقفکردن حلقههای بینهایت را نامبرد. ازسویدیگر معمولا برنامههای پیچیده و عملی درطول اجرا از تعدادی حالت مختلف عبور میکنند. توصیف رفتار برنامه بهعنوان ماشین حالت بسیارمفید است و برنامه را سادهتر میکند. با مفهوم ماشین حالت شما میتوانید برنامههای پیچیده را بهصورت منطقی و با خوانایی بالا بنویسید. ممکن است در ابتدا ماشین حالت را پیچیده بدانیم یا کاربرد آن را به موارد خاص محدود کنیم اما قصد داریم برتری نوشتن برنامه براساس مفهوم ماشین حالت را بیانکنیم. دراین مقاله روشهای مختلف پیادهسازی ماشین حالت در برنامه را دنبال خواهیمکرد.
ماشین حالت – Finite state machines
ماشین حالت، یک ابزار ریاضی برای توصیف پردازش توسط یک ماشین است و نحوهی واکنش ماشین به رویدادهای مختلف را بیان میکند. ماشین حالت را میتوان مدلی تشکیلشده از حالتها (State)، رویدادها ، انتقالها، اعمال و شرطها دانست. یک رویداد (Event)، اتفاقیاست که به ماشین حالت اعمال میشود و به آن ورودی ماشین (Input) نیز گفته میشود. نحوهی رفتار ماشین حالت به رویدادی خاص را انتقال (Transition) مینامند. در یک انتقال، مشخص میشود که ماشین حالت براساس حالت جاری خود، با دریافت یک رویداد، چه عکسالعملی را باید بروز دهد. درطی یک انتقال، ماشین از یکحالت بهحالتی دیگر منتقل خواهدشد. گاهی نیازاست پیشاز انجام عکسالعمل، شرطی بررسی شده و سپس انتقال رخ دهد، بهاینشرط، guard یا منطق شرطی (Conditional Logic) گفته میشود. درصورت درستبودن شرط، انتقال انجام میگیرد. یک عمل(action) بیانگر نحوه پاسخگویی ماشین حالت درطول دوره انتقال است.
نمودار حالت
نمودار حالت نوعی دیاگرام برای تشریح و توضیح رفتار سیستم است. این نمودار مجموعهای از رویدادها را که در حالات مختلف و ممکن یک سیستم رخ میدهد تجزیه و تحلیل میکند. در نمودار حالت، کلیه حالات یک ماشین در نظر گرفته میشود. نمودار حالت درواقع شکل بصری جدول حالت یک ماشین یا مدارمنطقی است و بهوسیله آن دیدبهتری را میتوان نسبتبه سیستم بهدستآورد. برنامهی ساده زمانسنج دیجیتالی را درنظربگیرید که دارای دو حالت آغاز زمان و پایان زمان است. درتصویرزیر نمودار ماشین حالت این برنامه را مشاهده میکنید.
1) توصیف رفتار ماشین حالت با دستورات شرطی
رایجترین روش پیادهسازی ماشین حالت در برنامهنویسی C، نوشتن منطق برنامه با دستورات شرطی switch-case و if-else است. در برنامهای که در ادامه آوردهشده با دستورات شرطی switch-case، حالات و رویدادهای برنامهی زمانسنج در نظر گرفتهشدهاست. در ابتدا حالتها و رویدادها را تعریف میکنیم. اگر رویدادی رخ دهد حالت کنونی بررسی میشود و باتوجهبه رویداد، حالت تغییر میکند و یک عمل اجرا میشود.
//-----------------------switch-case-------------------------------// typedef enum { stopped, started }State; typedef enum { stopEvent, startEvent }Event; State current_state; Event new_event; switch(current_state) { case started : if (new_event == stopEvent) { // action for stopevent current_state = stopped;// change state break; } else if (new_event == startEvent) { /* Already started do nothing. */ break; } case stopped : if (new_event == stopEvent) { /* Already stopped -> do nothing. */ break; } else if (new_event == startEvent) { // action for startevent current_state = stopped;// change state break; } default : error("Illegal state"); break; }
مزایای دستورات شرطی : برنامه ساده و قابلفهم است.
معایب دستورات شرطی : برنامه با دستورات شرطی مقیاسپذیر نیست یعنی برای تعداد حالتهای بیشتر عملی و کارآمد نخواهد بود. اگر در برنامههای دارای تعداد زیاد حالت از این روش استفادهکنیم کدبرنامه طولانی خواهدشد و درصورت تغییر در بخشیاز برنامه، تغییرکد بسیارمشکل است. در این نحوهی نوشتن کد، میان اجزای ماشین حالت مانند حالتها و رویدادها و عملها تفکیک واضحی وجود ندارد و درنتیجه دارای خوانایی کمی خواهدبود.
2) توصیف رفتار برنامه با جدول انتقال حالت
//--------------------------------------transition-table-----------------------------------// typedef enum { stopped, started }State; typedef enum { stopEvent, startEvent }Event; State state; #define NO_OF_STATES 2 #define NO_OF_EVENTS 2 static State TransitionTable[NO_OF_STATES][NO_OF_EVENTS]= { {stopped, started} {stopped, started} }; void startwatch(state) { const State currentState = state; state = TransitionTable[currentState][startEvent] } void stopwatch(state) { const State currentState = state; state = TransitionTable[currentState][stopEvent] }
مزایای جدول انتقال حالت: استفادهاز جدول انتقال حالت بهجای دستورات شرطی، باعث مقیاسپذیری برنامه میشود یعنی گسترش برنامه به تعداد حالتهای بیشتر امکانپذیر است و کد برنامه خوانایی بالاتری دارد چراکه از یک جدول انتقال برای تعریف ماشین حالت استفادهمیشود. برخلاف دستورات شرطی از ساختارهای تکراری استفاده نمیشود. هنگام تغییر برنامه یا اضافهکردن حالت بیشتر به برنامه تنها جدول انتقال تغییر میکند.
3) تفکیک حالتها در توابع
در کدی که ارائه میشود هر عمل (action) در یک تابع تعریف شده و برنامه بهصورت تفکیکشده است.
//------------------------function-per-action------------------------------// void action_s1_e1(); // current_state = stopped new_event = stopEvent void action s1_e2(); // current_state = stopped new_event = startEvent void action s2_e1(); // current_state = started new_event = stopEvent void action s2_e2(); // current_state = started new_event = startEvent void action_s1_e1() { /* do some processing here */ /* Already stopped */ /* set new state, if necessary */ } void action_s1_e2() { /* do some processing here */ // action for startevent current_state = started; /* set new state, if necessary */ } void action_s2_e1() { /* do some processing here */ // action for stopevent current_state = stoped; /* set new state, if necessary */ } void action_s2_e2() { /* do some processing here */ /* Already started */ /* set new state, if necessary */ }
منبع: سیسوگ