إخلاء المسؤولية: توضح هذه المقالة حلاً غير واضح لمشكلة غير واضحة. قبل التسرع بيض وضعها موضع التنفيذ ، أوصي بقراءة المقالة حتى النهاية والتفكير مرتين.

مرحبا بالجميع! عند العمل مع التعليمات البرمجية ، غالبًا ما يتعين علينا التعامل مع الحالة . إحدى هذه الحالات هي دورة حياة الأشياء. يمكن أن تكون إدارة كائن بعدة حالات محتملة مهمة غير تافهة للغاية. أضف تنفيذًا غير متزامن هنا والمهمة معقدة بأمر من الحجم. هناك حل فعال وطبيعي. في هذه المقالة سأتحدث عن آلة الحدث وكيفية تنفيذها في Go.
لماذا تدير الدولة؟
بادئ ذي بدء ، دعنا نحدد المفهوم نفسه. أبسط مثال على حالة: ملفات واتصالات مختلفة. لا يمكنك فقط أخذ ملف وقراءته. يجب فتحه أولاً ، وفي النهاية يفضل تأكد من إغلاقه. اتضح أن الإجراء الحالي يعتمد على نتيجة الإجراء السابق: تعتمد القراءة على الافتتاح. النتيجة المحفوظة هي الدولة.
المشكلة الرئيسية مع الدولة هي التعقيد. أي حالة تعقد التعليمات البرمجية تلقائيًا. عليك تخزين نتائج الإجراءات في الذاكرة وإضافة عمليات تحقق مختلفة إلى المنطق. هذا هو السبب في أن الهياكل عديمة الجنسية جذابة للغاية للمبرمجين - لا أحد يريد مشكلة الصعوبات. إذا كانت نتائج أفعالك لا تؤثر على منطق التنفيذ ، فأنت لست بحاجة إلى حالة.
ومع ذلك ، هناك خاصية واحدة تجعلك تحسب الصعوبات. تتطلب منك الدولة اتباع ترتيب محدد من الإجراءات. بشكل عام ، يجب تجنب مثل هذه الحالات ، ولكن هذا ليس ممكنًا دائمًا. ومن الأمثلة على ذلك دورة حياة كائنات البرنامج. بفضل الإدارة الجيدة للحالة ، يمكن للمرء الحصول على سلوك يمكن التنبؤ به للكائنات ذات دورة الحياة المعقدة.
الآن دعونا نتعرف على كيفية القيام بذلك.
آلي كوسيلة لحل المشاكل

عندما يتحدث الناس عن الحالات ، تتبادر إلى الذهن آلات الحالة المحدودة على الفور. هذا منطقي ، لأن الأوتوماتيكي هو الطريقة الأكثر طبيعية لإدارة الدولة.
لن أخوض في نظرية الأتمتة ، فهناك أكثر من معلومات كافية على الإنترنت.
إذا كنت تبحث عن أمثلة على آلات الحالة المحدودة لـ Go ، فسوف تقابل بالتأكيد lexer من Rob Pike . مثال رائع على آلة أوتوماتيكية تكون فيها البيانات التي تتم معالجتها هي الأبجدية المدخلة. هذا يعني أن انتقالات الحالة ناتجة عن النص الذي يعالج lexer. حل أنيق لمشكلة محددة.
الشيء الرئيسي الذي يجب فهمه هو أن الأوتوماتيكي هو حل لمشكلة محددة بدقة. لذلك ، قبل اعتبارها كعلاج لجميع المشاكل ، يجب أن تفهم المهمة تمامًا. على وجه التحديد ، الكيان الذي تريد التحكم فيه:
- الدول - دورة الحياة ؛
- الأحداث - بالضبط ما يسبب الانتقال إلى كل دولة ؛
- نتيجة العمل - بيانات الإخراج ؛
- وضع التنفيذ (متزامن / غير متزامن) ؛
- حالات الاستخدام الرئيسية.
إن جهاز lexer جميل ، لكنه يغير الحالة فقط بسبب البيانات التي يعالجها بنفسه. ولكن ماذا عن الموقف عندما يستدعي المستخدم التحولات؟ هذا هو المكان الذي يمكن أن تساعد فيه آلة الحدث.
مثال حقيقي
لتوضيح الأمر ، سأقوم بتحليل مثال من مكتبة phono
.
للحصول على الانغماس الكامل في السياق ، يمكنك قراءة المقالة التمهيدية . هذا ليس ضروريًا لهذا الموضوع ، ولكنه سيساعد على فهم أفضل لما نديره.
وماذا ندير؟
يعتمد phono
على خط أنابيب DSP. وتتكون من ثلاث مراحل للمعالجة. قد تتضمن كل مرحلة مكونًا واحدًا إلى عدة مكونات:

pipe.Pump
(المضخة الإنجليزية) هي مرحلة إلزامية لاستقبال الصوت ، دائمًا مكون واحد فقط.pipe.Processor
(معالج اللغة الإنجليزية) - مرحلة اختيارية لمعالجة الصوت ، من 0 إلى N من المكونات.pipe.Sink
(الحوض الإنجليزي) - مرحلة إلزامية لنقل الصوت ، من 1 إلى N من المكونات.
في الواقع ، سوف ندير دورة حياة الناقل.
دورة الحياة
هذا هو pipe.Pipe
مخطط pipe.Pipe
.

تشير الخطوط المائلة إلى التحولات الناتجة عن منطق التنفيذ الداخلي. غامق - التحولات الناجمة عن الأحداث. يوضح الرسم البياني أن الحالات مقسمة إلى نوعين:
- حالات هادئة -
ready
paused
، يمكنك القفز منها فقط حسب الحدث - الحالات النشطة -
running
pausing
مؤقت ، انتقالات حسب الحدث وبسبب منطق التنفيذ
قبل التحليل التفصيلي للكود ، مثال واضح على استخدام جميع الحالات:
الآن ، أول شيء أولاً.
كل كود المصدر متاح في المستودع .
الدول والأحداث
لنبدأ بأهم شيء.
بفضل الأنواع المنفصلة ، يتم أيضًا الإعلان عن التحولات بشكل منفصل لكل ولاية. هذا يتجنب الضخم النقانق وظائف الانتقال مع switch
المتداخلة. الدول نفسها لا تحتوي على أي بيانات أو منطق. بالنسبة لهم ، يمكنك التصريح عن المتغيرات على مستوى الحزمة بحيث لا تفعل ذلك في كل مرة. مطلوب واجهة state
لتعدد الأشكال. سنتحدث عن activeState
و idleState
بعد ذلك بقليل.
الجزء الثاني الأكثر أهمية من آلة لدينا هو الأحداث.
لفهم سبب الحاجة إلى نوع target
، فكر في مثال بسيط. لقد قمنا بإنشاء ناقل جديد ، فهو ready
. الآن قم بتشغيله مع p.Run()
. يتم إرسال حدث run
إلى الجهاز ، حيث يمر خط الأنابيب بحالة running
. كيف تعرف متى ينتهي الناقل؟ هذا هو المكان الذي سيساعدنا فيه نوع target
. يشير إلى حالة الراحة المتوقعة بعد الحدث. في مثالنا ، بعد الانتهاء من العمل ، سيدخل خط الأنابيب مرة أخرى في حالة ready
. نفس الشيء في الرسم البياني:

الآن المزيد عن أنواع الدول. بتعبير أدق ، عن activeState
و activeState
. لنلق نظرة على وظائف listen(*Pipe, target) (state, target)
لأنواع مختلفة من المراحل:
pipe.Pipe
له وظائف مختلفة لانتظار الانتقال! ماذا يوجد؟
وهكذا ، يمكننا الاستماع إلى قنوات مختلفة في ولايات مختلفة. على سبيل المثال ، يسمح لك هذا بعدم إرسال رسائل أثناء الإيقاف المؤقت - نحن فقط لا نستمع إلى القناة المقابلة.
منشئ وبدء تشغيل الجهاز

بالإضافة إلى التهيئة والخيارات الوظيفية ، هناك بداية جوروتين منفصل مع الدورة الرئيسية. حسنًا ، انظر إليه:
يتم إنشاء الناقل وتجميده تحسبًا للأحداث.
حان وقت العمل
استدعاء p.Run()
!

running
يولد رسائل وتشغيل حتى اكتمال خط الأنابيب.
وقفة
أثناء تنفيذ الناقل ، يمكننا إيقافه مؤقتًا. في هذه الحالة ، لن ينشئ خط الأنابيب رسائل جديدة. للقيام بذلك ، استدعاء الأسلوب p.Pause()
.

بمجرد أن يتلقى جميع المستلمين الرسالة ، سيدخل خط الأنابيب الحالة paused
. إذا كانت الرسالة هي الأخيرة ، فسيحدث الانتقال إلى حالة ready
.
العودة إلى العمل!
للخروج من الحالة paused
، اتصل p.Resume()
.

كل شيء تافه هنا ، يمر خط الأنابيب مرة أخرى إلى حالة running
.
انحنى
يمكن إيقاف الناقل من أي دولة. يوجد p.Close()
.

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