State machine چیست؟
State machine مدلی است بیانگر نحوه واکنش سیستم به وقایع مختلف. یک ماشین حالت وضعیت جاری قسمتی از سیستم را نگهداری کرده و به ورودیهای مختلف پاسخ میدهد. این ورودیها در نهایت وضعیت سیستم را تغییر خواهند داد.
نحوه پاسخگویی یک ماشین حالت (State machine) را به رویدادی خاص، انتقال (Transition) مینامند. در یک انتقال مشخص میشود که ماشین حالت بر اساس وضعیت جاری خود، با دریافت یک رویداد، چه عکس العملی را باید بروز دهد. عموما (و نه همیشه) در حین پاسخگویی ماشین حالت به رویدادهای رسیده، وضعیت آن نیز تغییر خواهد کرد. در اینجا گاهی از اوقات پیش از انجام عملیاتی، نیاز است شرطی بررسی شده و سپس انتقالی رخ دهد. به این شرط، guard گفته میشود.
بنابراین به صورت خلاصه، یک ماشین حالت، مدلی است از رفتاری خاص، تشکیل شده از حالات، رویدادها، انتقالات، اعمال (actions) و شرطها (Guards). در اینجا:
- یک حالت (State)، شرطی منحصربفرد در طول عمر ماشین حالت است. در هر زمان مشخصی، ماشین حالت در یکی از حالات از پیش تعریف شده خود قرار خواهد داشت.
- یک رویداد (Event)، اتفاقی است که به ماشین حالت اعمال میشود؛ یا همان ورودیهای سیستم.
- یک انتقال (Transition)، بیانگر نحوه رفتار ماشین حالت جهت پاسخگویی به رویداد وارده بر اساس وضعیت جاری خود میباشد. در طی یک انتقال، سیستم از یک حالت به حالتی دیگر منتقل خواهد شد.
- برای انجام یک انتقال، نیاز است یک شرط (Guard/Conditional Logic) بررسی شده و در صورت true بودن آن، انتقال صورت گیرد.
- یک عمل (Action)، بیانگر نحوه پاسخگویی ماشین حالت در طول دوره انتقال است.
چگونه میتوان الگوی ماشین حالت را تشخیص داد؟
اکثر برنامههای وب، متشکل از پیاده سازی چندین ماشین حالت میباشند؛ مانند ثبت نام در سایت، درخواست یک کتاب از کتابخانه، ارسال درخواستها و پاسخگویی به آنها و یا حتی ارسال یک مطلب در سایت، تائید و انتشار آن.
البته عموما در حین طراحی برنامهها، کمتر به این نوع مسایل به شکل یک ماشین حالت نگاه میشود. به همین جهت بهتر است معیارهایی را برای شناخت زود هنگام آنها مدنظر داشته باشیم:
- آیا در جدول بانک اطلاعاتی خود فیلدهایی مانند State (حالت) یا Status (وضعیت)دارید؟ اگر بله، به این معنا است که در حال کار با یک ماشین حالت هستید.
- عموما فیلدهای Bit و Boolean، بیانگر حضور ماشینهای حالت هستند. مانند IsPublished ، IsPaid و یا حتی داشتن یک فیلد timeStamp که میتواند NULL بپذیرد نیز بیانگر استفاده از ماشین حالت است؛ مانند فیلدهای published_at، paid_at و یا confirmed_at.
- داشتن رکوردهایی که تنها در طول یک بازه زمانی خاص، معتبر هستند. برای مثال آبونه شدن در یک سایت در طول یک بازه زمانی مشخص.
- اعمال چند مرحلهای؛ مانند ثبت نام در سایت و دریافت ایمیل فعال سازی. سپس فعال سازی اکانت از طریق ایمیل.
مثالی ساده از یک ماشین حالت
یک کلید برق را در نظر بگیرید. این کلید دارای دو حالت (states) روشن و خاموش است. زمانی که خاموش است، با دریافت رخدادی (event)، به وضعیت (state/status) روشن، منتقل خواهد شد (Transition) و برعکس.
در اینجا حالات با مستطیلهای گوشه گرد نمایش داده شدهاند. انتقالات توسط فلشهایی انحناء دار که حالات را به یکدیگر متصل میکنند، مشخص گردیدهاند. برچسبهای هر فلش، مشخص کننده نام رویدادی است که سبب انتقال و تغییر حالت میگردد. با شروع یک ماشین حالت، این ماشین در یکی از وضعیتهای از پیش تعیین شدهاش قرار خواهد گرفت (initial state)؛ که در اینجا حالت خاموش است.
این نوع نمودارها میتوانند شامل جزئیات بیشتری نیز باشند؛ مانند برچسبهایی که نمایانگر اعمال قابل انجام در طی یک انتقال هستند.
رسم ماشینهای حالت در برنامههای وب، به کمک کتابخانه jsPlumb
کتابخانههای زیادی برای رسم فلوچارت، گردشهای کاری، ماشینهای حالت و امثال آن جهت برنامههای وب وجود دارند و یکی از معروفترینهای آنها کتابخانه
jsPlumb است. این کتابخانه به صورت یک افزونه jQuery طراحی شده است؛ اما به عنوان افزونهای برای کتابخانههای MooTools و یا YUI3/Yahoo User Interface 3 نیز قابل استفاده میباشد. کتابخانه jsPlumb در مرورگرهای جدید از امکانات ترسیم SVG و یا HTML5 Canvas استفاده میکند. برای سازگاری با مرورگرهای قدیمیتر مانند IE8 به صورت خودکار به VML سوئیچ خواهد کرد. همچنین این کتابخانه امکانات ترسیم تعاملی قطعات به هم متصل شونده را نیز
دارا است (شبیه به طراح یک گردش کاری). البته برای اضافه شدن امکاناتی مانند کشیدن و رها کردن در آن نیاز به jQuery-UI نیز خواهد داشت.
برای نمونه اگر بخواهیم مثال فوق را توسط jsPlumb ترسیم کنیم، روش کار به صورت زیر خواهد بود:
<!doctype html>
<html>
<head>
<title>State Machine Demonstration</title>
<style type="text/css">
#opened
{
left: 10em;
top: 5em;
}
#off
{
left: 12em;
top: 15em;
}
#on
{
left: 28em;
top: 15em;
}
.w
{
width: 5em;
padding: 1em;
position: absolute;
border: 1px solid black;
z-index: 4;
border-radius: 1em;
border: 1px solid #346789;
box-shadow: 2px 2px 19px #e0e0e0;
-o-box-shadow: 2px 2px 19px #e0e0e0;
-webkit-box-shadow: 2px 2px 19px #e0e0e0;
-moz-box-shadow: 2px 2px 19px #e0e0e0;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
opacity: 0.8;
filter: alpha(opacity=80);
cursor: move;
}
.ep
{
float: right;
width: 1em;
height: 1em;
background-color: #994466;
cursor: pointer;
}
.labelClass
{
font-size: 20pt;
}
</style>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery-ui.min.js"></script>
<script type="text/javascript" src="jquery.jsPlumb-all-min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
jsPlumb.importDefaults({
Endpoint: ["Dot", { radius: 5}],
HoverPaintStyle: { strokeStyle: "blue", lineWidth: 2 },
ConnectionOverlays: [
["Arrow", { location: 1, id: "arrow", length: 14, foldback: 0.8}]
]
});
jsPlumb.makeTarget($(".w"), {
dropOptions: { hoverClass: "dragHover" },
anchor: "Continuous"
});
$(".ep").each(function (i, e) {
var p = $(e).parent();
jsPlumb.makeSource($(e), {
parent: p,
anchor: "Continuous",
connector: ["StateMachine", { curviness: 20}],
connectorStyle: { strokeStyle: '#42a62c', lineWidth: 2 },
maxConnections: 2,
onMaxConnections: function (info, e) {
alert("Maximum connections (" + info.maxConnections + ") reached");
}
});
});
jsPlumb.bind("connection", function (info) {
});
jsPlumb.draggable($(".w"));
jsPlumb.connect({ source: "opened", target: "off" });
jsPlumb.connect({ source: "off", target: "on", label: "Turn On" });
jsPlumb.connect({ source: "on", target: "off", label: "Turn Off" });
});
</script>
</head>
<body>
<div class="w" id="opened">
Begin
<div class="ep">
</div>
</div>
<div class="w" id="off">
Off
<div class="ep">
</div>
</div>
<div class="w" id="on">
On
<div class="ep">
</div>
</div>
</body>
</html>
مستندات کامل jsPlumb را
در سایت آن میتوان ملاحظه نمود.
در مثال فوق، ابتدا css و فایلهای js مورد نیاز ذکر شدهاند. توسط css، مکان قرارگیری اولیه المانهای متناظر با حالات، مشخص میشوند.
سپس زمانیکه اشیاء صفحه در دسترس هستند، تنظیمات jsPlumb انجام خواهد شد. برای مثال در اینجا نوع نمایشی Endpointها به نقطه تنظیم شده است. موارد دیگری مانند مستطیل نیز قابل تنظیم است. سپس نیاز است منبع و مقصدها به کتابخانه jsPlumb معرفی شوند. به کمک متد jsPlumb.makeTarget، تمام المانهای دارای کلاس w به عنوان منبع و با شمارش divهایی با class=ep، مقصدهای قابل اتصال تعیین شدهاند (jsPlumb.makeSource). متد jsPlumb.bind یک callback function است و هربار که اتصالی برقرار میشود، فراخوانی خواهد شد. متد jsPlumb.draggable تمام عناصر دارای کلاس w را قابل کشیدن و رها کردن میکند و در آخر توسط متدهای jsPlumb.connect، مقصد و منبعهای مشخصی را هم متصل خواهیم کرد.
نمونه نهایی تهیه شده برای بررسی بیشتر.
برای مطالعه بیشتر
Finite-state machine
UML state machine
UML 2 State Machine Diagrams
مثالهایی در این مورد