هر برنامهنویس پیش از اینکه نوشتن برنامه را آغاز کند اگر بتواند دیاگرام مسئله شامل ورودیها و حالتهای موردانتظار را مشخص کند در برنامهنویسی موفقتر خواهد بود.
حتی اگر بتوان این نوع تفکر را در ساختار کدنویسی خود وارد کنیم میتوانیم بسیاری از اشکالات برنامهنویسی خود را برطرف کنیم و برنامهای با سطح خوانایی بالا داشته باشیم.
در این پست قصد داریم به مفهوم ماشین حالت و کاربرد آن در برنامهنویسی بپردازیم.
یکی از مشکلات برنامهنویسان ایجاد حلقههای بینهایت در برنامه است. برای نمونه وجود تعداد زیادی وقفه و دستورات شرطی در برنامه ممکن است احتمال چنین خطایی را افزایش دهد.
گفتنی است حلقه بینهایت دنبالهای از دستورالعملها در یک برنامه است که به شکل نامحدودی تکرار میشود یا به این دلیل که شرطی برای پایان یافتن حلقه تعیین نشده، یا اگر تعیین شده، طوری است که آن شرایط هیچگاه اتفاق نمیافتد.
حلقههای بینهایت باعث میشوند تا برنامه کل زمان در دسترس پردازنده را مصرف کند، اما معمولاً کاربر میتواند آنها را از بین ببرد. یکی از دلایلی که میتواند باعث هنگ کردن سیستم شود همین حلقههای بینهایت هستند.
یکی از روشهای معمول برای رهایی از این خطا به کارگیری تایمرها و یا watchdog برای ریست سیستم است اما این روش به از دست رفتن داده یا رفتارهای ناخواستهی برنامه منجر میشود.
از کاربردهای ماشین حالت میتوان جلوگیری از اشکالات برنامهنویسی، آسانتر کردن اشکالزدایی برنامه و متوقف کردن حلقههای بینهایت را نام برد.
از سوی دیگر معمولا برنامههای پیچیده و عملی در طول اجرا از تعدادی حالت مختلف عبور میکنند. توصیف رفتار برنامه به عنوان ماشین حالت بسیار مفید است و برنامه را سادهتر میکند.
با مفهوم ماشین حالت شما میتوانید برنامههای پیچیده را به صورت منطقی و با خوانایی بالا بنویسید. ممکن است در ابتدا ماشین حالت را پیچیده بدانیم یا کاربرد آن را به موارد خاص محدود کنیم اما قصد داریم برتری نوشتن برنامه بر اساس مفهوم ماشین حالت را بیان کنیم.
در این مقاله روشهای مختلف پیادهسازی ماشین حالت در برنامه را دنبال خواهیم کرد.
ماشین حالت – Finite state machines
ماشین حالت، یک ابزار ریاضی برای توصیف پردازش توسط یک ماشین است و نحوهی واکنش ماشین به رویدادهای مختلف را بیان میکند. ماشین حالت را میتوان مدلی تشکیل شده از حالتها (State)، رویدادها ، انتقالها، اعمال و شرطها دانست.
یک رویداد (Event)، اتفاقی است که به ماشین حالت اعمال میشود و به آن ورودی ماشین (Input) نیز گفته میشود.
نحوهی رفتار ماشین حالت به رویدادی خاص را انتقال (Transition) مینامند. در یک انتقال، مشخص میشود که ماشین حالت بر اساس حالت جاری خود، با دریافت یک رویداد، چه عکس العملی را باید بروز دهد.
در طی یک انتقال، ماشین از یک حالت به حالتی دیگر منتقل خواهد شد.
گاهی نیاز است پیش از انجام عکسالعمل، شرطی بررسی شده و سپس انتقال رخ دهد، به این شرط، guard یا منطق شرطی (Conditional Logic) گفته میشود. در صورت درست بودن شرط، انتقال انجام میگیرد.
یک عمل(action) بیانگر نحوه پاسخگویی ماشین حالت در طول دوره انتقال است.
نمودار حالت
نمودار حالت نوعی دیاگرام برای تشریح و توضیح رفتار سیستم است.
این نمودار مجموعهای از رویدادها را که در حالات مختلف و ممکن یک سیستم رخ میدهد تجزیه و تحلیل میکند. در نمودار حالت، کلیه حالات یک ماشین در نظر گرفته میشود.
نمودار حالت در واقع شکل بصری جدول حالت یک ماشین یا مدار منطقی است و به وسیله آن دید بهتری را میتوان نسبت به سیستم به دست آورد.
برنامهی ساده زمانسنج دیجیتالی را در نظر بگیرید که دارای دو حالت آغاز زمان و پایان زمان است. در تصویر زیر نمودار ماشین حالت این برنامه را مشاهده میکنید.
1) توصیف رفتار ماشین حالت با دستورات شرطی
رایجترین روش پیادهسازی ماشین حالت در برنامهنویسی C، نوشتن منطق برنامه با دستورات شرطی switch-case و if-else است. در برنامهای که در ادامه آورده شده با دستورات شرطی switch-case، حالات و رویدادهای برنامهی زمانسنج در نظر گرفته شده است. در ابتدا حالتها و رویدادها را تعریف میکنیم. اگر رویدادی رخ دهد حالت کنونی بررسی میشود و با توجه به رویداد، حالت تغییر میکند و یک عمل اجرا میشود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //-----------------------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) توصیف رفتار برنامه با جدول انتقال حالت
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //--------------------------------------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) در یک تابع تعریف شده و برنامه به صورت تفکیک شده است.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //------------------------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 */ } |
بی زحمت ی مثال کامل از روش دوم با avr قرار بدید که بتونیم بفهمیم چیه دقیقا
سلام.
مطلب خوبی بود .
ولی معایب جدول انتقال حالت رو نفرمودید .
علی القاعده باید از منابع سیستم بیشتر استفاده کنه …
سلام تشکر بابت مطالب خوبتون عالیه
لطفا ادامه بدید
درضمن لطفا روحساب اینکه همه بلدند توضیح ندید
یکم بیشتر جامع تر و بامثال بیشتر ..
من حالت اول رو بلد بودم اما حالت دوم جالب بود اما بد نبود مثال دقیق تر و کاربردی تر(یعنی در مین اصلی استفاده شده )ی ارائه میدادید
جاداره ادامش بدید
سلام وقت به خیر ممنون از مطلب جالب
من کمی با مثالی که برای حالت جدولی آوردید مشکل دارم. تابع main برنامه کجاست و توابع action و start و stop کجا فراخوانی می شوند؟ اگر در یک وضعیتی بخواهیم یک وظیفه را به صورت ادامه دار انجام دهیم – مانند چک کردن وضعیت یک کلید ورودی – اون قسمت از کد باید کجا بیاد؟
ممنون
خوب این یه شبح برنامه است و برنامه کامل نیست که این جزییات رو داشته باشه :/