ربما تكون آلات الحالة المحدودة واحدة من أكثر المفاهيم الأساسية والمستخدمة على نطاق واسع في البرمجة. تستخدم آلات الحالة المحدودة (KA) بنشاط في العديد من المنافذ التطبيقية. على وجه الخصوص ، في منافذ مثل APCS والاتصالات ، والتي كان من الممكن التعامل معها ، تم العثور على المركبات الفضائية أقل قليلاً من كل خطوة.
لذلك ، في هذه المقالة سنحاول التحدث عن المركبات الفضائية ، في المقام الأول عن آلات الحالة المحدودة الهرمية وقدراتها المتقدمة.
وأخبرنا قليلاً عن دعم المركبة الفضائية في
SObjectizer-5 ، إطار عمل "الفاعل" لـ C ++. واحدة من تلك القلة التي تكون مفتوحة وحرة ومتعددة المنصات ولا تزال على قيد الحياة.
حتى إذا لم تكن مهتمًا بـ SObjectizer ، ولكنك لم تسمع أبدًا عن أجهزة الحالة المحدودة الهرمية أو مدى فائدة هذه الميزات المتقدمة للمركبة الفضائية مثل معالجات الإدخال / الإخراج للولايات أو تاريخ الحالة ، فقد تكون مهتمًا بالنظر تحت القطة و قراءة الجزء الأول من المقالة على الأقل.
كلمات عامة عن آلات الحالة المحدودة
لن نحاول إجراء برنامج تعليمي كامل في المقالة حول موضوع
الأتمتة ومجموعة متنوعة مثل
آلات الحالة المحدودة . يحتاج القارئ إلى أن يكون لديه على الأقل فهم أساسي لهذه الأنواع من الكيانات.
آلات الحالة المحدودة المتقدمة وقدراتها
تحتوي المركبة الفضائية على العديد من الميزات "المتقدمة" التي تزيد بشكل كبير من سهولة استخدام المركبة الفضائية في البرنامج. دعنا نلقي نظرة سريعة على هذه الميزات "المتقدمة".
إخلاء المسؤولية: إذا كان القارئ على دراية جيدة بمخططات الحالة من UML ، فلن يجد أي شيء جديد لنفسه هنا.
آلات الحالة الهرمية
ربما تكون الفرصة الأكثر أهمية وقيمة هي تنظيم هرمية / تعشيش الدول. نظرًا لأن القدرة على وضع الحالات في بعضها البعض على وجه التحديد هي التي تزيل "انفجار" عدد التحولات من دولة إلى أخرى مع زيادة تعقيد المركبة الفضائية.
من الصعب شرح ذلك بالكلمات أكثر من إظهاره بالقدوة. لذلك ، دعنا نتخيل أن لدينا infokiosk على الشاشة يتم عرض رسالة ترحيب لأول مرة. يمكن للمستخدم تحديد عنصر "الخدمات" والانتقال إلى القسم لاختيار الخدمات التي يحتاجها. أو يمكنه تحديد عنصر "الحساب الشخصي" والانتقال إلى قسم العمل مع بياناته وخدماته الشخصية. أو يمكنه اختيار قسم المساعدة. حتى الآن ، يبدو أن كل شيء بسيط ويمكن تمثيله بمخطط الحالة التالي (مبسط قدر الإمكان):

ولكن دعونا نحاول التأكد من أنه من خلال النقر على الزر "إلغاء" ، يمكن للمستخدم العودة من أي قسم إلى صفحة البداية برسالة ترحيب:

المخطط يزداد تعقيدًا ، لكنه لا يزال تحت السيطرة. ومع ذلك ، لنتذكر أنه في قسم "الخدمات" قد يكون لدينا عدة أقسام فرعية أخرى ، على سبيل المثال ، "الخدمات الشعبية" و "الخدمات الجديدة" و "القائمة الكاملة". ومن كل قسم من هذه الأقسام ، تحتاج أيضًا إلى العودة إلى صفحة البداية. أصبحت مركبتنا الفضائية البسيطة أكثر صعوبة:

لكن هذا أبعد ما يكون عن الكل. لم نأخذ في الاعتبار حتى الآن الزر "رجوع" ، الذي نحتاج من خلاله إلى العودة إلى القسم السابق. دعنا نضيف رد فعل على زر "العودة" ونرى ما نحصل عليه:

نعم ، الآن نرى الطريق إلى المتعة الحقيقية. ولكننا لم نفكر حتى في الأقسام الفرعية في قسمي "حسابي" و "مساعدة" ... إذا بدأنا ، فحينئذٍ ستتحول مركبتنا الفضائية البسيطة ، في البداية ، إلى شيء لا يمكن تصوره.
هنا يأتي تعشيش الدول لمساعدتنا. دعونا نتخيل أن لدينا حالتين فقط من المستوى الأعلى: WelcomeScreen و UserSelection. جميع أقسامنا (مثل "الخدمات" و "حسابي" و "المساعدة") ستكون "متداخلة" في حالة اختيار المستخدم. يمكنك القول بأن حالات ServicesScreen و ProfileScreen و HelpScreen ستكون تابعة لـ UserSelection. وبما أنهم أطفال ، سيرثون رد الفعل لبعض الإشارات من حالتهم الأبوية. لذلك ، يمكننا تحديد الاستجابة لزر الإلغاء في UserSelection. لكننا لسنا بحاجة لتحديد هذا التفاعل في جميع الحالات الفرعية. ما الذي يجعل مركبتنا الفضائية أكثر إيجازًا وفهمًا:

هنا يمكنك ملاحظة أن رد فعل "إلغاء" و "رجوع" حددناه في UserSelection. ويعمل هذا رد الفعل على زر "إلغاء الأمر" للجميع بدون استثناء الحالات الفرعية لاختيار المستخدم (بما في ذلك حالة فرعية أخرى لتحديد الخدمات المحددة). ولكن في الحالة الفرعية لـ ServicesSelection ، يكون رد الفعل على الزر "السابق" مختلفًا بالفعل - فالعودة ليست في WelcomScreen ، ولكن في ServicesScreen.
تسمى CAs التي تستخدم التسلسل الهرمي / التعشيش للحالات آلات الحالة المحدودة الهرمية (ICA).
رد فعل على الدخول / الخروج من / إلى الدولة
ميزة مفيدة للغاية هي القدرة على تعيين استجابة لدخول حالة معينة ، وكذلك رد فعل على الخروج من حالة. لذلك ، في المثال أعلاه مع infokiosk ، يمكن تعليق معالج لدخول كل من الحالات ، مما سيؤدي إلى تغيير محتويات شاشة infokiosk.
يمكن توسيع المثال السابق قليلاً. لنفترض أن لدينا دولتان فرعيتان في WelcomScreen: BrightWelcomScreen ، حيث سيتم إبراز الشاشة بشكل طبيعي ، و DarkWelcomScreen ، حيث سيتم تقليل سطوع الشاشة. يمكننا أن نجعل معالج إدخال DarkWelcomScreen سيخفت الشاشة. ومعالج خروج DarkWelcomScreen الذي سيعيد السطوع الطبيعي.

التغيير التلقائي للحالة بعد وقت محدد
في بعض الأحيان ، قد يكون من الضروري الحد من بقاء المركبة الفضائية في حالة معينة. لذا ، في المثال أعلاه ، يمكننا تحديد الوقت الذي تظل فيه ICA في حالة BrightWelcomScreen بدقيقة واحدة. بمجرد انتهاء الدقيقة ، يتحول ICA تلقائيًا إلى حالة DarkWelcomScreen.
تاريخ المركبة الفضائية
ميزة أخرى مفيدة للغاية لـ ICA هي تاريخ حالة المركبة الفضائية.
دعونا نتخيل أن لدينا نوعًا ما من ICA التجريدي من هذا النوع:

يمكن أن ينتقل ICA هذا من TopLevelState1 إلى TopLevelState2 والعكس صحيح. ولكن داخل TopLevelState1 هناك العديد من الحالات المتداخلة. إذا انتقل ICA ببساطة من TopLevelState2 إلى TopLevelState1 ، فسيتم تنشيط حالتين على الفور: TopLevelState1 و NestedState1. تم تنشيط NestedState1 لأنها الحالة الفرعية الأولية لحالة TopLevelState1.
تخيل الآن أنه كلما غيرت ICA حالتها من NestedState1 إلى NestedState2. داخل NestedState2 ، تم تنشيط SubState InternalState1 (حيث أنها الحالة الفرعية الأولية لـ NestedState2). ومن InternalState1 ذهبنا إلى InternalState2. وبالتالي ، لدينا الحالات التالية نشطة في نفس الوقت: TopLevelState1 و NestedState2 و InternalState2. وهنا ننتقل إلى TopLevelState2 (أي أننا غادرنا بشكل عام TopLevelState1).
يصبح Active TopLevelState2. وبعد ذلك نريد العودة إلى TopLevelState1. يوجد في TopLevelState1 ، وليس في أي دولة فرعية معينة في TopLevelState1.
لذا ، من TopLevelState2 نذهب إلى TopLevelState1 وأين نصل؟
إذا لم يكن لـ TopLevelState1 أي سجل ، فسنأتي إلى TopLevelState1 و NestedState1 (نظرًا لأن NestedState1 هي الحالة الفرعية الأولية لـ TopLevelState1). على سبيل المثال القصة الكاملة عن التحولات داخل TopLevelState1 ، التي حدثت قبل مغادرة TopLevelState2 ، قد ضاعت تمامًا.
إذا كان TopLevelState1 لديه ما يسمى ب التاريخ الضحل ، ثم عند العودة من TopLevelState2 إلى TopLevelState1 ، نصل إلى NestedState2 و InternalState1. وصلنا إلى NestedState2 لأنه تم تسجيله في سجل حالة TopLevelState1. ونصل إلى InternalState1 لأنه هو البداية لـ NestedState2. اتضح أنه في التاريخ السطحي لـ TopLevelState1 ، يتم تخزين المعلومات فقط حول الحالة الثانوية للمستوى الأول. لا يتم الحفاظ على تاريخ الحالات المضمنة في هذه الحالات الفرعية.
ولكن إذا كان لـ TopLevelState1 تاريخًا عميقًا ، فعندما نعود من TopLevelState2 إلى TopLevelState1 ، فإننا ننتقل إلى NestedState2 و InternalState2. لأنه في التاريخ العميق ، يتم تخزين معلومات كاملة عن الحالات الفرعية النشطة ، بغض النظر عن عمقها.
الدول المتعامدة
حتى الآن ، قمنا بفحص ICA حيث يمكن أن يكون هناك دولة واحدة فقط نشطة داخل الدولة. ولكن في بعض الأحيان قد تكون هناك حالات عندما يكون في حالة معينة من ICA يجب أن يكون هناك العديد من الحالات الفرعية النشطة في وقت واحد. تسمى هذه الحالات الثانوية الدول المتعامدة.
المثال الكلاسيكي الذي يوضح الحالات المتعامدة هو لوحة مفاتيح الكمبيوتر المألوفة وأوضاع NumLock و CapsLock و ScrollLock. يمكننا القول أن العمل مع NumLock / CapsLock / ScrollLock موصوف من قبل الدول المتعامدة الفرعية داخل الحالة النشطة:

كل ما تريد معرفته عن آلات الحالة المحدودة ، ولكن ...
بشكل عام ، هناك مقالة تأسيسية عن التدوين الرسمي لمخططات الدولة من ديفيد هاريل:
المخططات الإحصائية: الشكلية البصرية للأنظمة المعقدة (1987) .
هناك ، يتم فحص المواقف المختلفة التي يمكن مواجهتها عند العمل مع أجهزة الحالة المحدودة باستخدام مثال التحكم في الساعة الإلكترونية العادية. إذا لم يقرأه أحد ، أوصي به بشدة. في الأساس ، ذهب كل شيء وصفه Harel بعد ذلك إلى تدوين UML. ولكن عندما تقرأ وصف الرسوم البيانية للحالة من UML ، فأنت لا تفهم دائمًا ماذا ولماذا ومتى تحتاج إليه. ولكن في مقال Harel ، ينتقل العرض التقديمي من مواقف بسيطة إلى مواقف أكثر تعقيدًا. وأنت على دراية أفضل بكل القوة التي تخفيها آلات الحالة المحدودة في حد ذاتها.
آلات الحالة المحدودة في SObjectizer
علاوة على ذلك ، سنتحدث عن SObjectizer وتفاصيله. إذا كنت لا تفهم تمامًا الأمثلة أدناه ، فقد يكون من المنطقي معرفة المزيد حول SObjectizer. على سبيل المثال ، من
مقال المراجعة حول SObjecizer والعديد من
المقالات اللاحقة التي تقدم القراء إلى SObjectizer ، والانتقال من البسيط إلى المعقد (المقالة
الأولى والثانية والثالثة ).
الوكلاء في SObjectizer هم آلات الدولة
كان الوكلاء في SObjectizer منذ البداية آلات حالة ذات حالات صريحة. حتى إذا لم يصف مطور الوكيل أيًا من حالاته الخاصة في فئة الوكيل الخاصة به ، فإن الوكيل لا يزال لديه حالة افتراضية ، والتي تم استخدامها افتراضيًا. على سبيل المثال ، إذا قام أحد المطورين بعمل وكيل تافه مثل:
class simple_demo final : public so_5::agent_t { public:
عندها قد لا يشك في أنه في الواقع جميع الاشتراكات التي قام بها تتم من أجل الحالة الافتراضية. ولكن إذا أضاف المطور حالاته الخاصة إلى الوكيل ، فعليك بالفعل التفكير في توقيع الوكيل بشكل صحيح في الحالة الصحيحة. هنا ، على سبيل المثال ، تعديل بسيط (وكالعادة) للعامل الموضح أعلاه:
class simple_demo final : public so_5::agent_t {
قمنا بتعيين معالجين مختلفين لإشارة how_are_you ، لكل منهما لحالته الخاصة.
والخطأ في تعديل وكيل simple_demo هذا هو أنه في حالة st_free أو st_busy ، لن يستجيب الوكيل لإنهاء العمل على الإطلاق ، لأن تركنا اشتراك الإقلاع في الحالة الافتراضية ، لكننا لم نجعل الاشتراكات المقابلة لـ st_free و st_busy. طريقة بسيطة وواضحة لحل هذه المشكلة هي إضافة الاشتراكات المناسبة إلى st_free و st_busy:
simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} {
صحيح ، هذه الطريقة تتفوق على لصق النسخ ، وهو ليس جيدًا. يمكنك التخلص من النسخ واللصق عن طريق إدخال حالة أصل مشتركة لـ st_free و st_busy:
class simple_demo final : public so_5::agent_t {
من أجل العدالة ، يجب أن يضاف أنه في البداية يمكن أن يكون عملاء SObjectizer مجرد آلات دولة بسيطة. ظهر دعم المركبات الفضائية الهرمية مؤخرًا نسبيًا ، في يناير 2016.
لماذا وكلاء SObjectizer آلات الحالة المحدودة؟
يحتوي هذا السؤال على إجابة بسيطة للغاية:
حدث ذلك أن جذور SObjectizer تنمو من عالم أنظمة التحكم في العمليات ، وهناك آلات حالة محدودة تستخدم في كثير من الأحيان. لذلك ، اعتبرنا أنه من الضروري أن تكون العوامل الموجودة في SObjectizer هي آلات الحالة. هذا مريح للغاية إذا تم استخدام CAs في التطبيق الذي يحاولون تطبيق SObjectizer. والحالة الافتراضية ، التي يمتلكها جميع العملاء ، تسمح لنا بعدم التفكير في المركبات الفضائية إذا لم يكن استخدام المركبة الفضائية مطلوبًا.
من حيث المبدأ ، إذا نظرت إلى نموذج الممثلين نفسه ، وإلى المبادئ التي بني عليها هذا النموذج:
- الفاعل هو كيان له سلوك ؛
- الجهات الفاعلة الرد على الرسائل الواردة ؛
- بعد استلام الرسالة ، يمكن للممثل:
- إرسال عدد معين من الرسائل إلى الجهات الفاعلة الأخرى ؛
- إنشاء عدد من الجهات الفاعلة الجديدة ؛
- تحديد سلوك جديد لمعالجة الرسائل اللاحقة.
يمكن للمرء أن يجد تشابهًا قويًا بين المركبات الفضائية البسيطة والممثلين. يمكنك حتى أن تقول أن الممثلين هم آلات دولة محدودة بسيطة.
ما ميزات أجهزة الحالة المتقدمة التي يدعمها SObjectizer؟
من الميزات المذكورة أعلاه لآلات الحالة المحدودة المتقدمة ، يدعم SObjectizer كل شيء باستثناء الحالات المتعامدة. الأشياء الجيدة الأخرى ، مثل الولايات المتداخلة ، ومعالجات الإدخال / الإخراج ، والقيود المفروضة على الوقت المستغرق في الولاية ، والتاريخ للولايات ، مدعومة.
بدعم من الدول المتعامدة ، لم تنمو أول مرة معًا. من ناحية ، لم يكن المقصود من البنية الداخلية لـ SObjectizer دعم العديد من الحالات المستقلة والنشطة في نفس الوقت للعامل. من ناحية أخرى ، هناك أسئلة أيديولوجية حول كيفية تصرف الوكيل الذي لديه حالات متعامدة. تبين أن تشابك هذه الأسئلة معقد للغاية ، وكان العادم المفيد صغيرًا جدًا لحل هذه المشكلة. نعم ، وفي ممارستنا ، لم تكن هناك حالات تتطلب الحالات المتعامدة ، ولكن سيكون من المستحيل القيام بذلك ، على سبيل المثال ، من خلال تقسيم العمل بين عدة عوامل مرتبطة بسياق عمل مشترك واحد.
ومع ذلك ، إذا كان شخص ما بحاجة إلى ميزة مثل حالات التعامد ، ولديك أمثلة من العالم الحقيقي للمهام التي تتطلب ذلك ، فلنتحدث. ربما ، لدينا أمثلة ملموسة أمام أعيننا ، يمكننا إضافة هذه الميزة إلى SObjectizer.
كيف يبدو دعم الميزات المتقدمة لـ ICA في التعليمات البرمجية
في هذا الجزء من القصة ، سنحاول الانتقال سريعًا إلى SObjectizer-5 API للعمل مع ICA. دون التعمق في التفاصيل ، فقط حتى يكون لدى القارئ فكرة عما هو وكيف يبدو. يمكن العثور على معلومات أكثر تفصيلا ، إذا كنت ترغب في ذلك ،
في الوثائق الرسمية .
الدول المتداخلة
من أجل الإعلان عن حالة متداخلة ، تحتاج إلى تمرير التعبير الأولي_ substate_of أو substate_of إلى مُنشئ كائن state_t المقابل:
class demo : public so_5::agent_t { state_t st_parent{this};
إذا كانت الحالة S تحتوي على العديد من الحالات الفرعية C1 و C2 و ... و Cn ، فيجب وضع علامة على إحدى هذه الحالات (وواحدة فقط) على أنها أولية فرعية. يتم تشخيص انتهاك هذه القاعدة في وقت التشغيل.
الحد الأقصى لعمق تداخل الحالة في SObjectizer-5 محدود. في الإصدارات 5.5 ، هذه المستويات 16. يتم تشخيص انتهاك هذه القاعدة في وقت التشغيل.
الحيلة الأكثر أهمية مع الحالات المتداخلة هي أنه عندما يتم تنشيط حالة تحتوي على حالات متداخلة ، يتم تنشيط العديد من الحالات في وقت واحد. لنفترض أن هناك حالة A تحتوي على ركائز فرعية B و C ، وفي الحالة الفرعية B توجد ركائز فرعية D و E:

عندما يتم تنشيط الحالة أ ، في الواقع ، يتم تنشيط ثلاث حالات على الفور: A و AB و ABD
إن حقيقة أن العديد من الدول يمكن أن تكون نشطة في آن واحد لها أخطر تأثير على شيئين أرشيفيين. أولاً ، للبحث عن معالج للرسالة الواردة التالية. لذلك ، في المثال الموضح للتو ، سيتم البحث عن معالج الرسائل أولاً في حالة ABD. إذا لم يكن هناك معالج مناسب هناك ، فسيستمر البحث في حالته الأصلية ، أي في AB وتضر بالفعل ، إذا لزم الأمر ، سيستمر البحث في الدولة A.
ثانيًا ، يؤثر وجود العديد من الحالات النشطة على ترتيب استدعاء معالجات الإدخال / الإخراج للولايات. ولكن سيتم مناقشة هذا أدناه.
معالجات I / O الدولة
بالنسبة للولاية ، يمكن تحديد معالجات حالة الدخول والخروج من الولاية. يتم ذلك باستخدام أساليب state_t :: on_enter و state_t :: on_exit. عادة ، يتم استدعاء هذه الطرق في طريقة so_define_agent () (أو مباشرة في مُنشئ الوكيل إذا كان الوكيل تافهًا ولم يتم توفير الميراث منه).
class demo : public so_5::agent_t { state_t st_free{this}; state_t st_busy{this}; ... void so_define_agent() override {
ربما تكون اللحظة الأكثر صعوبة مع معالجات on_enter / on_exit هي استخدامها للحالات المتداخلة. دعنا نعود إلى المثال مع الحالات A و B و C و D و E.

افترض أن لكل ولاية معالج on_enter و on_exit.
دع A. تصبح الحالة الحالية للعامل. يتم تنشيط الحالات A و AB و ABD أثناء تغيير حالة الوكيل ، سيتم استدعاء A.on_enter و ABon_enter و ABDon_enter. وبهذا الترتيب.
لنفترض أن هناك انتقال إلى ABE. سيتم استدعاء ABDon_exit و ABEon_enter.
إذا وضعنا الوكيل في حالة AC ، فسيتم استدعاء ABEon_exit و ABon_exit و ACon_enter.
إذا تم إلغاء تسجيل العامل الموجود في حالة التيار المتردد ، فسيتم استدعاء معالجات ACon_exit و A.on_exit فور الانتهاء من طريقة so_evt_finish ().
حدود الوقت
يتم تعيين الحد الزمني للبقاء الوكيل في حالة معينة باستخدام أسلوب state_t :: time_limit. كما هو الحال مع on_enter / on_exit ، عادة ما يتم استدعاء طرق time_limit حيث يتم تكوين الوكيل للعمل داخل SObjectizer:
class led_indicator : public so_5::agent_t { state_t inactive{this}; state_t active{this}; ... void so_define_agent() override {
إذا تم تعيين الحد الزمني للولاية ، فعندما يدخل الوكيل هذه الحالة ، يبدأ SObjectizer في حساب الوقت المستغرق في الولاية. إذا غادر الوكيل الحالة ، ثم عاد إلى هذه الحالة مرة أخرى ، فسيبدأ العد التنازلي مرة أخرى.
إذا تم تعيين حدود زمنية للحالات المضمنة ، فأنت بحاجة إلى توخي الحذر ، لأن الحيل الغريبة ممكنة:
class demo : public so_5::agent_t {
افترض أن وكيلًا يدخل في الحالة أ. يتم تنشيط الدولتين A و C لكل من A و C. في السابق ، سينتهي بالحالة C وسيتحول الوكيل إلى الحالة D. سيبدأ هذا العد التنازلي للبقاء في الحالة D. ولكن سيستمر العد التنازلي للبقاء في A! نظرًا لأنه خلال فترة الانتقال من C إلى D ، استمر العامل في البقاء في الحالة A. وبعد خمس ثوانٍ من الانتقال القسري من C إلى D ، سيذهب الوكيل إلى الحالة B.
قصة ثروة
بشكل افتراضي ، ليس لدى الدول الوكيل تاريخ. لتنشيط حفظ التاريخ لدولة ما ، قم بتمرير ثابت الضحلة (الدولة سيكون لها تاريخ ضحل) أو تاريخ عميق (الدولة سيكون لها تاريخ عميق) إلى مُنشئ state_t. على سبيل المثال:
class demo : public so_5::agent_t { state_t A{this, shallow_history}; state_t B{this, deep_history}; ... };
يعد تاريخ الولايات موضوعًا صعبًا ، خاصة عند استخدام عمق لائق لتعشيش الدول وللدول الفرعية تاريخها الخاص. لذلك ، للحصول على معلومات أكثر اكتمالاً حول هذا الموضوع ، من الأفضل الرجوع
إلى الوثائق ، إلى التجربة. حسنًا ، أن تسألنا إذا كنت لا تستطيع معرفة ذلك بنفسك ؛)
just_switch_to، transfer_to_state، قمع
تحتوي فئة state_t على عدد من الأساليب الأكثر استخدامًا التي تم عرضها أعلاه بالفعل: event () لاشتراك الأحداث في رسالة ، on_enter () و on_exit () لتعيين معالجات الإدخال / الإخراج ، time_limit () لتعيين حد للوقت المستغرق في الولاية.
إلى جانب هذه الأساليب ، عند العمل مع ICA ، فإن الطرق التالية لفئة state_t مفيدة جدًا:
الطريقة just_switch_to () ، المصممة للحالة عندما يكون رد الفعل الوحيد على الرسالة الواردة هو نقل الوكيل إلى حالة جديدة. يمكنك الكتابة:
some_state.just_switch_to<some_msg>(another_state);
بدلاً من:
some_state.event([this](mhood_t<some_msg>) { this >>= another_state; });
تُعد طريقة transfer_to_state () مفيدة جدًا عندما تتم معالجة بعض الرسائل M بنفس الطريقة في حالتين أو أكثر S1 ، S2 ، ... ، Sn. ولكن ، إذا كنا في الولايات S2 ، ... ، Sn ، فعلينا أولاً العودة إلى S1 ، وعندها فقط نقوم بمعالجة M.
إذا كان هذا يبدو صعبًا ، فربما في مثال رمز ، سيتم فهم هذا الموقف بشكل أفضل:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
ولكن بدلاً من تحديد معالجات الأحداث المشابهة جدًا لـ S2 ، ... ، Sn ، استخدم transfer_to_state:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
تمنع الطريقة suppress () بحث معالج الأحداث عن الحالة الفرعية الحالية وجميع الحالات الفرعية الرئيسية الخاصة بها. افترض أن لدينا حالة أصل A يتم فيها استدعاء std :: abort () في الرسالة M. وهناك حالة تابعة لـ B يمكن فيها تجاهل M بأمان. يجب تحديد رد الفعل على M في الحالة الفرعية B ، لأنه إذا لم نفعل ذلك ، فسيتم العثور على معالج B في A. لذلك ، سنحتاج إلى كتابة شيء مثل:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.event([](mhood_t<M>) {});
تتيح لك طريقة suppress () كتابة هذا الموقف في التعليمات البرمجية بشكل أكثر وضوحًا ورسوماتًا:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.suppress<M>();
مثال بسيط جدا
تتضمن الأمثلة القياسية لـ SObjectizer v.5.5 مثالًا بسيطًا ،
blinking_led ، والذي يحاكي تشغيل مؤشر LED الوامض. مخطط حالة الوكيل من هذا المثال هو كما يلي:

وإليك رمز الوكيل الكامل من هذا المثال:
class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } };
هنا ، يتم تنفيذ جميع الأعمال الفعلية داخل معالجات الإدخال / الإخراج للحالة الفرعية blink_on. حسنًا ، بالإضافة إلى ذلك ، يحد من مدة الإقامة في العمل الثانوي في blink_on و blink_off.
ليس مثال بسيط للغاية
تتضمن الأمثلة القياسية لـ SObjectizer v.5.5 أيضًا مثالًا أكثر تعقيدًا بكثير ،
intercom_statechart ، والذي يحاكي سلوك لوحة هاتف الباب. ويظهر مخطط الحالة للعامل الرئيسي في هذا المثال شيئًا مثل هذا:

كل شيء قاسي للغاية لأن هذا التقليد لا يدعم فقط الاتصال بشقة عن طريق الرقم ، ولكن أيضًا أشياء مثل رمز سري فريد لكل شقة ، بالإضافة إلى رمز خدمة خاص. تسمح لك هذه الرموز بفتح قفل الباب دون طلب أي مكان.
لا تزال هناك أشياء مثيرة للاهتمام في هذا المثال. لكنها كبيرة جدًا بحيث لا يمكن وصفها بالتفصيل (حتى المقالة المستقلة قد لا تكون كافية لذلك). لذلك إذا كنت مهتمًا بمظهر ICA المعقد حقًا في SObjectizer ، يمكنك أن ترى في هذا المثال. وإذا كان هناك شيء غير واضح ، فيمكنك طرح سؤال علينا. على سبيل المثال ، في التعليقات على هذه المقالة.
هل من الممكن عدم استخدام دعم المركبة الفضائية المدمج في SObjectizer-5؟
لذلك ، يحتوي SObjectizer-5 على دعم مضمن لـ ICA مع مجموعة واسعة جدًا من الميزات المدعومة. يتم هذا الدعم بالطبع من أجل استخدامه. على وجه الخصوص ، آليات تصحيح SObjectizer ، مثل
تتبع تسليم الرسائل ، تدرك حالة الوكيل وتعرض الحالة الحالية في رسائل التصحيح الخاصة بها.
ومع ذلك ، إذا لم يرغب المطور لسبب ما في استخدام أدوات SObjectizer-5 المدمجة ، فقد لا يفعل ذلك.
على سبيل المثال ، يمكنك رفض استخدام SObjectizer state_t وأشياء أخرى مثله لأن state_t عبارة عن كائن ثقيل جدًا به داخل std :: string ، وزوج من std :: function ، والعديد من العدادات مثل std :: size_t ، خمس مؤشرات لأشياء مختلفة وبعض التفاهات الأخرى. معا ، هذا على لينكس 64 بت و GCC-5.5 ، على سبيل المثال ، يعطي 160 بايت لكل حالة (بصرف النظر عما يمكن تخصيصه في الذاكرة الديناميكية).
إذا كنت بحاجة ، على سبيل المثال ، إلى مليون وكيل في التطبيق ، كل منها سيكون له 10 حالات ، فقد لا يكون الحمل الإضافي لـ SObjectizer state_t مقبولًا. في هذه الحالة ، يمكنك استخدام آلية أخرى للعمل مع أجهزة الحالة ، وتفويض معالجة الرسالة يدويًا لهذه الآلية. شيء مثل:
class external_fsm_demo : public so_5::agent_t { some_fsm_type my_fsm_; ... void so_define_agent() override { so_subscribe_self() .event([this](mhood_t<msg_one> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_two> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_three> cmd) { my_fsm_.handle(*cmd); }); ... } ... };
في هذه الحالة ، أنت تدفع مقابل الكفاءة من خلال زيادة حجم العمل اليدوي وعدم وجود مساعدة من آليات تصحيح SObjectizer. ولكن هنا يعود الأمر للمطور ليقرر.
الخلاصة
تحولت المقالة إلى أنها ضخمة ، أكثر بكثير مما كان مخططا في الأصل. شكرا لكل من قرأ على هذا المكان. إذا رأى أحد القراء أنه من الممكن ترك ملاحظاتك في التعليقات على المقالة ، فسيكون ذلك رائعًا.
إذا بقي شيء ما غير واضح ، فقم بطرح الأسئلة ، وسوف نجيب بسعادة.
أيضًا ، مع اغتنام هذه الفرصة ، أود أن ألفت انتباه أولئك المهتمين بـ SObjectizer ، حيث بدأ هذا العمل في الإصدار التالي من SObjectizer في إطار الفرع 5.5. باختصار حول ما يعتبر للتنفيذ في 5.5.23 ، الموصوفة هنا . بشكل كامل ، ولكن باللغة الإنجليزية ، هنا . يمكنك ترك رأيك حول أي من الميزات المقترحة للتنفيذ ، أو تقديم شيء آخر. على سبيل المثال
هناك فرصة حقيقية للتأثير على تطوير SObjectizer. علاوة على ذلك ، بعد إصدار v.5.5.23 ، قد يكون هناك توقف مؤقت في العمل على SObjectizer وقد لا تكون الفرصة القادمة لتضمين شيء مفيد في 2018 SObjectizer موجودة.