"أنا أحب المعكرونة الغربيين ، وأنا أكره رمز المعكرونة"يعد "رمز السباغيتي" تعبيرًا مثاليًا لوصف البرامج التي تمثل فوضى تبخر من وجهة نظر معرفية وجمالية. في هذه المقالة ، سأتحدث عن خطة من ثلاث نقاط لتدمير رمز السباغيتي:
- نناقش لماذا رمز السباغيتي ليس لذيذًا جدًا.
- تقديم نظرة جديدة على ما يفعله الكود بالفعل.
- نناقش Frame Machine Notation (FMN) ، مما يساعد المطورين على كشف كرة من اللصق.
نعلم جميعًا مدى صعوبة قراءة رمز شخص آخر. قد يكون هذا بسبب حقيقة أن المهمة نفسها صعبة أو لأن بنية الكود هي أيضًا "إبداعية". غالبًا ما تسير هاتان المشكلتان جنبًا إلى جنب.
التحديات هي مهام صعبة ، وعادة ما لا يوجد سوى اكتشاف ثوري يمكنه تبسيطها. ومع ذلك ، يحدث أن يضيف هيكل البرنامج نفسه تعقيدات غير ضرورية ، وهذه المشكلة
تستحق الحل .
يكمن قبح كود السباغيتي في منطقه الشرطي المعقد. وعلى الرغم من صعوبة تصور الحياة دون وجود العديد من التركيبات الصعبة ، إلا أن هذه المقالة ستوضح لك حلاً أفضل.
لتوضيح الموقف مع رمز السباغيتي ، نحتاج أولاً إلى تشغيل هذا:
كرسبي باستافي هذا:
ال دينت!لنبدأ الطهي.
الدولة الضمنية
لصنع المعكرونة ، نحن بالتأكيد بحاجة إلى الماء للطهي. ومع ذلك ، يمكن أن يكون هذا العنصر البسيط الذي يشتمل على شفرة معكرونة مربكًا للغاية.
هنا مثال بسيط:
(temp < 32)
ماذا هذا الاختيار تفعل حقا؟ من الواضح أنه يقسم خط الأرقام إلى جزأين ، ولكن ماذا
تعني هذه الأجزاء؟ أعتقد أنه يمكنك القيام بافتراض منطقي ، ولكن المشكلة هي أن الكود لا ينقل هذا
بشكل صريح بالفعل.
إذا أكدت حقًا أنها تتحقق مما إذا كانت المياه صلبة
[تقريبًا. حارة: وفقًا لمقياس فهرنهايت ، يتجمد الماء عند +32 درجة] ، ما الذي يعنيه منطقياً العائد كاذب؟
if (temp < 32) {
على الرغم من أن عملية التحقق قسمت الأرقام إلى مجموعتين ، إلا أن هناك في الواقع ثلاث حالات منطقية - صلبة وسائلة وغازية (صلبة ، سائلة ، غازية)!
هذا هو ، رقم السطر:
تقسيم حسب حالة الاختيار على النحو التالي:
if (temp < 32) {
} else {
}
لاحظ ما حدث لأنه مهم جدًا لفهم طبيعة رمز السباغيتي. قسم التقييم المنطقي قسم مساحة الرقم إلى جزأين ، لكنه لم يصنف النظام كهيكل منطقي حقيقي من (SOLID ، LIQUID ، GAS). بدلاً من ذلك ، قسم التحقق المساحة إلى (SOLID ، كل شيء آخر).
هنا فحص مماثل:
if (temp > 212) {
بصريا ، سيبدو كما يلي:
if (temp > 212) {
} else {
}
لاحظ أن:
- لم يتم الإعلان عن المجموعة الكاملة من الحالات المحتملة في أي مكان
- في أي مكان في البنى الشرطية يتم الإعلان عن حالات منطقية يمكن التحقق منها أو إعلان مجموعات من الدول
- يتم تجميع بعض الدول بشكل غير مباشر بواسطة بنية المنطق الشرطي والتفرع
هذا الرمز هش ، ولكنه شائع جدًا ، وليس كبيرًا بحيث يسبب مشاكل في دعمه. لذلك دعونا نجعل الوضع أسوأ.
لم يعجبني رمزك على أي حاليتضمن الرمز الموضح أعلاه وجود ثلاث حالات للمادة - SOLID و LIQUID و GAS. ومع ذلك ، وفقا للبيانات العلمية ، في الواقع ، هناك
أربع حالات يمكن ملاحظتها يتم فيها تضمين البلازما (PLASMA) (في الواقع ، هناك العديد من الحالات الأخرى ، ولكن هذا سيكون كافيا بالنسبة لنا). على الرغم من أنه لا يوجد أحد يقوم بتحضير عجينة من البلازما ، إلا أنه إذا تم نشر هذا الكود على جيثب ، وبعد ذلك تم تفرعه من قبل بعض طلاب الدراسات العليا الذين يدرسون فيزياء الطاقة العالية ، فسيتعين علينا الحفاظ على هذه الحالة أيضًا.
ومع ذلك ، عند إضافة البلازما ، فإن الكود الموضح أعلاه سوف يقوم بسذاجة بما يلي:
if (temp < 32) {
من المرجح أن الشفرة القديمة ، عند إضافتها إلى العديد من حالات البلازما ، سوف تنكسر في الفروع الأخرى. لسوء الحظ ، لا يوجد شيء في بنية التعليمات البرمجية يساعد في الإبلاغ عن وجود حالة جديدة أو التأثير على التغييرات. بالإضافة إلى ذلك ، من المحتمل أن تكون أي أخطاء غير واضحة ، أي أن العثور عليها سيكون الأكثر صعوبة. فقط قل لا للحشرات في السباغيتي.
باختصار ، المشكلة هي هذه: يتم استخدام الاختبارات المنطقية لتحديد الحالات
بشكل غير مباشر . غالبًا ما لا يتم إعلان الحالات المنطقية وغير مرئية في التعليمات البرمجية. كما رأينا أعلاه ، عندما يضيف النظام حالات منطقية جديدة ، قد ينقطع الكود الموجود. لتجنب هذا الأمر ،
يجب على المطورين إعادة فحص كل فحص شرطي وفرعي للتأكد من أن مسارات التعليمات البرمجية لا تزال صالحة
لجميع حالاتها المنطقية! هذا هو السبب الرئيسي لتدهور أجزاء الكود الكبيرة حيث تصبح أكثر تعقيدًا.
على الرغم من أنه لا توجد طرق للتخلص تمامًا من عمليات التحقق من البيانات الشرطية ، فإن أي أسلوب يقللها يقلل من تعقيد الكود.
دعنا الآن نلقي نظرة على تطبيق نموذجي وجوه المنحى من الطبقة التي تخلق نموذجا بسيطا
للغاية لحجم الماء. سيقوم الفصل بإدارة التغييرات في حالة المياه. بعد دراسة مشاكل الحل الكلاسيكي لهذه المشكلة ، نناقش بعد ذلك ترميزًا جديدًا يسمى
Frame ونعرض كيف يمكنه التغلب على الصعوبات التي اكتشفناها.
أولا إحضار الماء حتى الغليان ...
أعطى العلم أسماء لجميع التحولات المحتملة التي يمكن أن تقوم بها المادة عندما تتغير درجة الحرارة.
صفنا بسيط جدًا (وليس مفيدًا بشكل خاص). يستجيب لتحديات أداء التحولات بين الحالات ويغير درجة الحرارة حتى تصبح مناسبة للحالة المستهدفة المرغوبة:
(ملاحظة: لقد كتبت هذا الرمز الزائف. استخدمه في عملك فقط على مسؤوليتك وحدتك.)
class WaterSample { temp:int Water(temp:int) { this.temp = temp }
بالمقارنة مع المثال الأول ، يحتوي هذا الرمز على بعض التحسينات. أولاً ، يتم استبدال الأرقام "السحرية" ذات الترميز الثابت (32 ، 212) بثوابت حدود درجة حرارة الحالة (WATER_SOLID_TEMP ، WATER_GAS_TEMP). يبدأ هذا التغيير في جعل الولايات أكثر وضوحًا ، وإن كان بشكل غير مباشر.
تظهر الشيكات أيضًا عن "البرمجة الدفاعية" في هذا الرمز ، والتي تقيد استدعاء الطريقة إذا كانت في حالة غير مناسبة للعملية. على سبيل المثال ، لا يمكن تجميد الماء إذا لم يكن سائلاً - وهذا ينتهك قانون (الطبيعة). ولكن إضافة شروط الوكالة الدولية للطاقة يعقد تعقيد فهم الغرض من الكود. على سبيل المثال:
هذا الفحص الشرطي يقوم بما يلي:
- يتحقق إذا كانت
temp
أقل من درجة حرارة الغاز الحد - يتحقق إذا كانت
temp
تتجاوز درجة حرارة الحد الصلبة - إرجاع خطأ إذا كان أحد هذه الاختبارات غير صحيح
هذا المنطق مربك. أولاً ، أن يكون المرء في حالة سائلة يتحدد بما
لا تحتويه المادة - أي مادة صلبة أو غازية.
(temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP)
ثانياً ، يتحقق الكود لمعرفة ما إذا كانت المياه سائلة لمعرفة ما إذا كان هناك حاجة إلى إرجاع خطأ.
!(temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP)
أول مرة لفهم هذا النفي المزدوج للدول ليست سهلة. هنا تبسيط يقلل قليلاً من تعقيد التعبير:
bool isLiquidWater = (temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP) if (!isLiquidWater) throw new IllegalStateError()
يسهل فهم هذا الرمز لأن حالة
isL LiquidWater صريحة .
الآن نحن نستكشف التقنيات التي تحدد حالة
واضحة كأفضل طريقة لحل المشاكل. مع هذا النهج ، تصبح الحالات المنطقية للنظام هي البنية المادية للبرنامج ، مما يحسن الكود ويبسط فهمه.
تدوين آلة الإطار
إن Frame Machine Notation (FMN) هي لغة خاصة بمجال معين (لغة خاصة بالمجال ، DSL) تُعرِّف المقاربة الفئوية والمنهجية والبسيطة لتحديد أنواع مختلفة من
الآلات وتنفيذها. من أجل البساطة ، سأدعو Frame automata ببساطة "آلات" ، لأن هذا الترميز يمكن أن يحدد المعايير النظرية لأي أنواع مختلفة (آلات الحالة ، وأتمتة المتجر ، والتطور العلوي لآلات أتمتة - تورينج). لمعرفة أنواع الأجهزة المختلفة وتطبيقاتها ، أوصي بدراسة الصفحة على
ويكيبيديا .
على الرغم من أن نظرية الأتمتة قد تكون مثيرة للاهتمام (بيان مشكوك فيه للغاية) ، في هذه المقالة سوف نركز على التطبيق العملي لهذه المفاهيم القوية لبناء النظم وكتابة التعليمات البرمجية.
لحل هذه المشكلة ، يقدم Frame تدوينًا قياسيًا يعمل على ثلاثة مستويات متكاملة:
- نص DSL لتحديد وحدات تحكم الإطار مع بناء جملة أنيقة وموجزة
- مجموعة من أنماط الترميز المرجعية لتنفيذ فئات موجهة للكائنات في شكل آلات يطلق عليها Frame "وحدات التحكم"
- التدوين المرئي الذي يستخدم فيه FMN للتعبير عن العمليات المعقدة التي يصعب تمثيلها بيانياً - الإطار المرئي للإطار (FVN)
في هذه المقالة ، سأنظر في النقطتين الأوليين: FMN وأنماط المرجع ، وسأترك مناقشة FVN للمقالات المستقبلية.
الإطار هو تدوين له عدة جوانب مهمة:
- تحتوي FMN على كائنات من المستوى الأول تتعلق بمفهوم الأتمتة ، والتي لا تتوفر بلغات موجهة للكائنات.
- تحدد مواصفات FMN أنماط التنفيذ القياسية في الشفرة الزائفة التي توضح كيفية تنفيذ تدوين FMN.
- سيتمكن FMN قريبًا من الترجمة (العمل مستمر) بأي لغة موجهة
ملاحظة: يتم استخدام التطبيق المرجعي لإظهار التكافؤ المطلق لتدوين FMN وطريقة بسيطة لتنفيذه في أي لغة موجهة. يمكنك اختيار أي طريقة.
الآن سوف أعرضك على أهم عنصرين من المستوى الأول في الإطار -
أحداث الإطار ووحدات
التحكم بالإطار .
أحداث الإطار
FrameEvents هي جزء لا يتجزأ من بساطة تدوين FMN. يتم تطبيق FrameEvent كهيكل أو فئة تحتوي على الأقل على متغيرات الأعضاء التالية:
- معرف الرسالة
- قاموس أو قائمة المعلمات
- كائن العودة
هنا هو الكود الكاذب للفئة FrameEvent:
class FrameEvent { var _msg:String var _params:Object var _return:Object FrameEvent(msg:String, params:Object = null) { _msg = msg _params = params } }
يستخدم تدوين الإطار الرمز
@ ، الذي يحدد كائن FrameEvent. كل سمة من سمات FrameEvent المطلوبة لها رمز مميز للوصول إليها:
@|message| : - _msg @[param1] : [] @^ : _return
غالبًا ما لا يتعين علينا تحديد ما يعمل عليه FrameEvent. نظرًا لأن معظم السياقات تعمل مع FrameEvent واحد فقط في المرة الواحدة ، يمكن بالتأكيد تبسيط الترميز بحيث يستخدم محددات السمة فقط. لذلك ، يمكننا تبسيط الوصول:
|buttonClick|
قد يبدو هذا التدوين غريبًا في البداية ، ولكن سرعان ما سنرى كيف أن بناء الجملة البسيط للأحداث يبسط فهم كود FMN بشكل كبير.
وحدات تحكم الإطار
وحدة التحكم في الإطارات هي فئة موجهة للكائنات ، مرتبة بطريقة محددة جيدًا لتنفيذ آلة الإطار. يتم تحديد أنواع أدوات التحكم بواسطة البادئة
# :
#MyController
هذا يكافئ الكود الكاذب الموجه إلى الكائن التالي:
class MyController {}
من الواضح أن هذه الفئة ليست مفيدة بشكل خاص. حتى يتمكن من فعل شيء ما ، يحتاج جهاز التحكم إلى حالة واحدة على الأقل للرد على الأحداث.
يتم تصميم وحدات التحكم بطريقة تحتوي على كتل من أنواع مختلفة ، والتي يتم تحديدها بواسطة شرطة تحيط اسم نوع الكتلة:
#MyController<br> -block 1- -block 2- -block 3-
لا يمكن أن تحتوي وحدة التحكم على أكثر من مثيل واحد لكل كتلة ، ويمكن أن تحتوي أنواع الكتل على أنواع معينة فقط من المكونات الفرعية. في هذه المقالة ، نقوم بفحص كتلة
الماكينة فقط ، والتي يمكن أن تحتوي على حالات فقط. يتم تعريف الدول بواسطة الرمز المميز للبادئة
$ .
هنا نرى FMN لوحدة تحكم تحتوي على جهاز مع حالة واحدة فقط:
#MyController
فيما يلي تطبيق رمز FMN أعلاه:
class MyController {
يتكون تنفيذ آلة الكتلة من العناصر التالية:
- متغير _state ، والذي يشير إلى وظيفة الحالة الحالية. يتم تهيئة مع وظيفة الدولة الأولى في وحدة تحكم.
- طريقة دولة واحدة أو أكثر
يتم تعريف طريقة حالة الإطار كدالة بالتوقيع التالي:
func MyState(e:FrameEvent);
بعد تحديد هذه الأساسيات لتطبيق كتلة الماكينة ، يمكننا أن نرى مدى تفاعل كائن FrameEvent مع الجهاز.
وحدة واجهة
إن تفاعل FrameEvents الذي يتحكم في تشغيل الآلة هو جوهر بساطة وقوة تدوين الإطار. ومع ذلك ، لم نقم بالإجابة على السؤال ، من أين يأتي FrameEvents - كيف يدخلون إلى وحدة التحكم للتحكم فيه؟ خيار واحد: يمكن للعملاء الخارجيين أنفسهم إنشاء وتهيئة FrameEvents ، ثم استدعاء الطريقة المشار إليها بواسطة متغير عضو _state مباشرة:
myController._state(new FrameEvent("buttonClick"))
سيكون البديل الأفضل هو إنشاء واجهة مشتركة تلتف مكالمة مباشرة إلى متغير عضو _state:
myController.sendEvent(new FrameEvent("buttonClick"))
ومع ذلك ، تتمثل الطريقة الأكثر خلوًا من المتاعب ، والتي تتوافق مع الطريقة المعتادة لإنشاء برامج موجهة للكائنات ، في إنشاء طرق شائعة لإرسال حدث نيابة عن العميل إلى الجهاز الداخلي:
class MyController { func buttonClick() { FrameEvent e = new FrameEvent("buttonClick") _state(e) return e._return } }
يحدد الإطار بناء جملة
كتلة واجهة تحتوي على طرق تحول المكالمات إلى واجهة عامة لـ FrameEvents.
#MyController -interface- buttonClick ...
تحتوي كتلة
interface
على العديد من الميزات الأخرى ، لكن هذا المثال يعطينا فكرة عامة عن كيفية عمل هذا. سأقدم شرحًا إضافيًا في المقالات التالية من السلسلة.
الآن دعنا نواصل دراسة تشغيل إطار آلي.
معالجات الأحداث
على الرغم من أننا أظهرنا كيفية تعريف سيارة ، إلا أننا لا نملك بعد ترميزًا
لفعل أي شيء. لمعالجة الأحداث ، نحتاج إلى 1) لتكون قادرًا على تحديد الحدث الذي يجب معالجته و 2) لإرفاقه بالسلوك الذي يتم تنفيذه.
فيما يلي وحدة تحكم بسيطة في الإطار توفر البنية الأساسية لمعالجة الأحداث:
#MyController
كما ذكر أعلاه ، للوصول إلى سمة
_msg
لحدث
_msg
، يستخدم تدوين FMN الأقواس من الخطوط العمودية:
|messageName|
يستخدم FMN أيضًا رمز مميز يمثل بيان الإرجاع. سيتم تطبيق وحدة التحكم الموضحة أعلاه على النحو التالي:
class MyController {
هنا نرى مدى وضوح تدوين FMN يتوافق مع نمط التنفيذ الذي يسهل فهمه وترميزه.
بعد تعيين هذه الجوانب الأساسية للأحداث ووحدات التحكم والآلات والحكومات ومعالجات الأحداث ، يمكننا المضي قدمًا في حل المشكلات الحقيقية بمساعدتهم.
آلات التركيز واحد
أعلاه نظرنا إلى وحدة تحكم عديمي الجنسية التي كانت عديمة الفائدة جدا.
#MyController
خطوة واحدة أعلى في السلسلة الغذائية للمرافق هي فئة ذات حالة واحدة ، والتي ، على الرغم من أنها غير مجدية ، فهي ببساطة مملة. لكنه على الأقل يفعل
شيئًا على الأقل.
أولاً ، دعنا نرى كيف سيتم تنفيذ فصل مع حالة واحدة فقط (ضمنية):
class Mono { String status() { return "OFF" } }
لا يتم الإعلان عن أي ولاية أو حتى ضمنية هنا ، ولكن دعنا نفترض أنه في حالة عمل الكود ، يكون النظام في حالة "العمل".
سنقدم أيضًا فكرة مهمة: سيتم اعتبار مكالمات الواجهة مماثلة لإرسال حدث إلى كائن. لذلك ، يمكن اعتبار الكود أعلاه وسيلة لنقل | الحالة | الطبقة مونو ، ودائما في حالة العامل دولار.
يمكن تصور هذا الموقف باستخدام جدول ربط الأحداث:
الآن دعونا نلقي نظرة على FMN ، والتي توضح نفس الوظيفة ومطابقة جدول الربط نفسه:
#Mono -machine- $Working |status| ^("OFF")
إليك ما يبدو عليه التنفيذ:
class Mono {
يمكنك ملاحظة أننا قدمنا أيضًا تدوينًا جديدًا
لبيان الإرجاع ، مما يعني تقييم التعبير وإعادة النتيجة إلى الواجهة:
^(return_expr)
هذا المشغل يعادل
@^ = return_expr
او فقط
^ = return_expr
جميع عوامل التشغيل هذه مكافئة وظيفيًا ويمكنك استخدام أي منها ، لكن
^(return_expr)
تبدو الأكثر تعبيرًا.
بدوره على الموقد
حتى الآن لقد رأينا وحدة تحكم مع 0 ولاية ووحدة تحكم مع 1 دولة. لم تكن مفيدة للغاية بعد ، لكننا بالفعل على وشك شيء مثير للاهتمام.
لطهي المعكرونة لدينا ، تحتاج أولاً إلى تشغيل الموقد. فيما يلي فئة تبديل بسيطة مع متغير منطقي واحد:
class Switch { boolean _isOn; func status() { if (_isOn) { return "ON"; } else { return "OFF"; } } }
على الرغم من أن هذا غير واضح للوهلة الأولى ، إلا أن الكود الموضح أعلاه يطبق جدول روابط الأحداث التالي:
للمقارنة ، هنا هو FMN لنفس السلوك:
#Switch1 -machine- $Off |status| ^("OFF") $On |status| ^("ON")
الآن نرى مدى تطابق تدوين الإطار مع غرض الكود الخاص بنا - إرفاق حدث (استدعاء الأسلوب) بالسلوك استنادًا إلى الحالة التي توجد بها وحدة التحكم. بالإضافة إلى ذلك ، يتوافق هيكل التنفيذ أيضًا مع جدول الربط:
class Switch1 {
يتيح لك الجدول فهم هدف وحدة التحكم في حالاتها المختلفة بسرعة. يتمتع كل من هيكل تدوين الإطار ونمط التنفيذ بمزايا مماثلة.
ومع ذلك ، لدينا التبديل لديه مشكلة وظيفية ملحوظة. تتم تهيئة في حالة $ Off ، ولكن لا يمكن التبديل إلى حالة $ On! للقيام بذلك ، نحتاج إلى إدخال مشغل
تغيير الحالة .
تغيير الدولة
بيان تغيير الحالة هو كما يلي:
->> $NewState
الآن يمكننا استخدام هذا المشغل للتبديل بين $ Off و $ On:
#Switch2 -machine- $Off |toggle| ->> $On ^ |status| ^("OFF") $On |toggle| ->> $Off ^ |status| ^("ON")
وهنا هو جدول ربط الحدث المقابل:
حدث جديد | الآن يؤدي إلى التغيير الذي يدور ببساطة بين الدولتين. كيف يمكن تنفيذ عملية تغيير الدولة؟
لا يوجد مكان أسهل. هنا هو تنفيذ Switch2:
class Switch2 {
يمكنك أيضًا إجراء التحسين الأخير في Switch2 بحيث لا يسمح لك فقط بالتبديل بين الحالات ، ولكن أيضًا يحدد الحالة بوضوح:
#Switch3 -machine- $Off |turnOn| ->> $On ^ |toggle| ->> $On ^ |status| ^("OFF") $On |turnOff| ->> $Off ^ |toggle| ->> $Off ^ |status| ^("ON")
بخلاف الحدث | تبديل | ، إذا | turnOn | يتم إرساله عندما يكون Switch3 قيد التشغيل بالفعل أو | turnOff | عند إيقاف تشغيله بالفعل ، يتم تجاهل الرسالة ولا يحدث شيء.
يمنح هذا التحسين الصغير العميل القدرة على الإشارة بوضوح إلى الحالة التي يجب أن يكون فيها المفتاح: class Switch3 {
توضح الخطوة الأخيرة في تطور المحول لدينا مدى سهولة فهم الغرض من وحدة تحكم FMN. توضح التعليمة البرمجية ذات الصلة مدى سهولة تنفيذها باستخدام آليات الإطار.بعد إنشاء ماكينة التبديل ، يمكننا تشغيل النار وبدء الطهي!حالة الصوت
يتمثل الجانب الرئيسي ، وإن كان دقيقًا ، في الأتمتة في أن الحالة الحالية للجهاز هي نتيجة إما موقف (على سبيل المثال ، قيد التشغيل) أو نوع من تحليل البيانات أو البيئة. عندما تحول الجهاز إلى الحالة المطلوبة ، فهذا يعني ضمنيًا. أن الوضع لن يتغير دون معرفة السيارة.ومع ذلك ، هذا الافتراض ليس صحيحا دائما. في بعض المواقف ، يلزم التحقق (أو "الاستشعار") للبيانات لتحديد الحالة المنطقية الحالية:- الحالة المستعادة الأولية - عند استعادة الجهاز من حالة ثابتة
- الحالة الخارجية - تُعرّف "الوضع الفعلي" الموجود في البيئة وقت إنشاء الجهاز أو استعادته أو تشغيله
- حالة داخلية متقلبة - عندما يتغير جزء من البيانات الداخلية التي تديرها آلة قيد التشغيل خارج سيطرة الجهاز
في جميع هذه الحالات ، يجب "اختبار" البيانات أو البيئة أو كليهما لتحديد الوضع وتعيين حالة الجهاز وفقًا لذلك. من الناحية المثالية ، يمكن تنفيذ هذا المنطق المنطقي في وظيفة واحدة تحدد الحالة المنطقية الصحيحة. لدعم هذا النمط ، يحتوي تدوين الإطار على نوع خاص من الوظائف التي تستكشف الكون وتحدد الموقف في الوقت الحالي. يشار إلى هذه الوظائف بالبادئة $ قبل اسم الطريقة التي تُرجع ارتباطًا للحالة : $probeForState()
في وضعنا ، يمكن تنفيذ هذه الطريقة على النحو التالي: func probeForState():FrameState { if (temp < 32) return Solid if (temp < 212) return Liquid return Gas }
كما نرى ، تقوم الطريقة ببساطة بإرجاع إشارة إلى وظيفة الحالة المقابلة للحالة المنطقية الصحيحة. يمكن بعد ذلك استخدام وظيفة الاستشعار هذه لإدخال الحالة الصحيحة: ->> $probeForState()
تبدو آلية التنفيذ كما يلي: _state = probeForState()
طريقة استشعار الحالة هي مثال لتدوين الإطار لإدارة الحالة بطريقة معينة. بعد ذلك ، سوف نتعلم أيضًا الترميز المهم لإدارة FrameEvents.الميراث السلوكي و المرسل
الوراثة السلوكية والمرسل نموذج برمجة قوي والموضوع الأخير حول تدوين الإطار في هذه المقالة.يستخدم الإطار استخدام وراثة السلوك ، وليس وراثة البيانات أو السمات الأخرى. بالنسبة لهذه الحالة ، يتم إرسال FrameEvents إلى ولايات أخرى إذا لم تتعامل الحالة الأولية مع الحدث (أو ، كما سنرى في المقالات التالية ، فقط تريد تمريره). هذه السلسلة من الأحداث يمكن أن تذهب إلى أي عمق المطلوب.لهذا ، يمكن تنفيذ الآلات باستخدام تقنية تسمى طريقة تسلسل الطريقة . تدوين FMN لإرسال الأحداث من ولاية إلى أخرى هو المرسل => : $S1 => $S2
يمكن تنفيذ بيان FMN هذا على النحو التالي: func S1(e:FrameEvent) { S2(e)
الآن نرى كم هو سهل لسلسلة أساليب الحالة. لنطبق هذه التقنية على موقف صعب إلى حد ما: #Movement -machine- $Walking => $Moving |getSpeed| ^(3) |isStanding| ^(true) $Running => $Moving |getSpeed| ^(6) |isStanding| ^(true) $Crawling => $Moving |getSpeed| ^(.5) |isStanding| ^(false) $AtAttention => $Motionless |isStanding| ^(true) $LyingDown => $Motionless |isStanding| ^(false) $Moving |isMoving| ^(true) $Motionless |getSpeed| ^(0) |isMoving| ^(false)
في التعليمة البرمجية أعلاه ، نرى أن هناك حالتين أساسيتين - $ Moving و $ Motionless - والدول الخمس الأخرى ترث وظائف مهمة منها. يوضح لنا ربط الحدث بوضوح كيف ستبدو الروابط بشكل عام:بفضل التقنيات التي تعلمناها ، سيكون التنفيذ بسيطًا جدًا: class Movement {
آلة المياه
الآن لدينا أساسيات المعرفة حول FMN ، مما يسمح لنا بفهم كيفية إعادة تنفيذ فئة WaterSample مع الدول وبطريقة أكثر ذكاءً. سنجعلها مفيدة أيضًا لفيزيائي طلاب الدراسات العليا لدينا ونضيف إليها حالة بلازما دولار جديدة:إليك ما يبدو عليه تطبيق FMN الكامل: #WaterSample -machine- $Begin |create|
كما ترون ، لدينا الحالة الأولية لـ $ Begin ، والتي تستجيب للرسالة | create | ويحتفظ القيمة temp
. تقوم وظيفة الاستشعار أولاً بفحص القيمة الأولية temp
لتحديد الحالة المنطقية ، ثم تقوم بإجراء انتقال الجهاز إلى هذه الحالة.جميع الحالات المادية ($ Solid ، $ Liquid ، $ Gas ، $ Plasma) ترث السلوك الوقائي من الحالة الافتراضية $. يتم تمرير جميع الأحداث غير الصالحة للحالة الحالية إلى الحالة الافتراضية $ ، والتي تلقي خطأ InvalidStateError. يوضح هذا كيف يمكن تنفيذ البرمجة الدفاعية البسيطة باستخدام الميراث السلوكي.والآن التنفيذ: class WaterSample {
استنتاج
يعد Automata مفهومًا أساسيًا لعلوم الكمبيوتر تم استخدامه لفترة طويلة جدًا في المجالات المتخصصة لتطوير البرمجيات والأجهزة. تتمثل المهمة الرئيسية لـ Frame في إنشاء تدوين لوصف الأتمتة وإعداد أنماط بسيطة لكتابة التعليمات البرمجية أو "الآليات" لتنفيذها. آمل أن يؤدي تدوين الإطار إلى تغيير الطريقة التي ينظر بها المبرمجون إلى الأجهزة ، مما يوفر طريقة سهلة لوضعها موضع التنفيذ في مهام البرمجة اليومية ، وبالطبع ، حفظها من السباغيتي في الكود.المنهي يأكل المعكرونة (صورة لسوزوكي سان)في المقالات المستقبلية ، استنادًا إلى المفاهيم التي تعلمناها ، سنخلق قوة أكبر وتعبيرًا عن تدوين FMN. مع مرور الوقت ، سأوسّع النقاش ليشمل دراسة النمذجة البصرية ، والتي تشمل شبكة FMN وحل مشاكل السلوك غير المؤكد في الأساليب الحديثة لتصميم البرمجيات.