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

يجب أن يكون التطبيق سريعًا ومتسامحًا مع الخطأ وحجمه جيدًا.
يبدو وكأنه "نحن للجميع الخير مقابل كل شيء سيء" ، أليس كذلك؟
ما المقصود بهذه الكلمات:
- استجابة
يجب أن يعطي التطبيق المستخدم النتيجة في نصف ثانية. يتضمن هذا أيضًا مبدأ الفشل بسرعة - أي أنه عندما يحدث خطأ ما ، من الأفضل أن تعود إلى المستخدم رسالة خطأ مثل "عذرًا ، حدثت مشكلة. حاول مرة أخرى لاحقًا لجعل الطقس في انتظار البحر. إذا كانت العملية طويلة ، نعرض للمستخدم شريط تقدم. إذا كان طويلًا جدًا - "سيتم تنفيذ طلبك مؤقتًا في 18 مارس 2042. سنرسل لك إشعارًا في البريد ". - التدرجية هي وسيلة لتوفير استجابة تحت الحمل. تخيل دورة حياة خدمة ناجحة نسبيًا:
- إطلاق - تدفق الطلب صغير ، يتم تشغيل الخدمة على جهاز افتراضي بنواة واحدة.
- يزداد تدفق الطلبات - تتم إضافة kernels إلى الجهاز الظاهري وتتم معالجة الطلبات في عدة مؤشرات ترابط.
- تحميل أكثر - نقوم بتوصيل التجميع - يتم تجميع طلبات قاعدة البيانات والقرص الصلب.
- تحميل أكثر - تحتاج إلى زيادة عدد الخوادم وتوفير العمل في المجموعة.
من الناحية المثالية ، يجب أن يرتفع النظام نفسه لأعلى أو لأسفل حسب الحمل.
- خطأ التسامح
نحن نقبل أننا نعيش في عالم غير كامل ويحدث كل شيء. في حالة حدوث خطأ ما في نظامنا ، يجب أن نقدم طرق معالجة الأخطاء والاسترداد - وأخيرًا ، نحن مدعوون لتحقيق كل هذا باستخدام نظام تعتمد بنيته على الرسائل التي تستند إلى الرسائل
قبل المتابعة ، أريد أن أتحدث عن مدى اختلاف النظم التي تحرك الأحداث عن الأنظمة التي تعتمد على الرسائل.
يحركها الحدث:- حدث - أبلغ النظام أنه قد وصل إلى حالة معينة.
- يمكن أن يكون هناك العديد من المشتركين في الحدث.
- سلسلة الأحداث عادةً ما تكون قصيرة ، وتكون معالجات الأحداث قريبة (جسديًا وكودًا) من المصدر.
- عادة ما يكون لمصدر الحدث ومعالجاته حالة عامة (فعليًا - يستخدمون نفس ذاكرة الوصول العشوائي لتبادل المعلومات).
على النقيض من الحدث ، في نظام يحركه الرسائل:- تحتوي كل رسالة على مستلم واحد فقط.
- الرسائل غير قابلة للتغيير: لا يمكنك تغيير أي شيء في الرسالة المستلمة بحيث يعرف المرسل عنها ويمكنه قراءة المعلومات.
- تستجيب عناصر النظام (أو لا تستجيب) لتلقي الرسائل ويمكنها إرسال رسائل إلى عناصر أخرى من النظام.
كل هذا يقدم لنا
نموذج الممثل
معالم التنمية:
- أول ذكر للممثلين هو في ورقة علمية عام 1973 - كارل هيويت ، وبيتر بيشوب ، وريتشارد شتيغر ، "شكليات ممثل عالمي نموذجي للذكاء الاصطناعي" ،
- 1986 - ظهر إرلانج. احتاج إريكسون إلى لغة معدات الاتصالات التي من شأنها أن توفر التسامح مع الخطأ وانتشار الأخطاء. في سياق هذا المقال ، معالمه الرئيسية هي:
- كل شيء عملية
- الرسائل هي السبيل الوحيد للاتصال (Erlang هي لغة وظيفية ، والرسائل فيها غير قابلة للتغيير).
- ..
- 2004 - الإصدار الأول من لغة Scala. معالمه:
- مدعوم من JVM ،
- وظيفية
- للترابط المتعدد ، تم اختيار نموذج ممثل.
- 2009 - تم تخصيص تنفيذ الجهات الفاعلة في مكتبة منفصلة - عكا
- 2014 - Akka.net - تم نقله إلى .Net.
ماذا يمكن أن تفعل الجهات الفاعلة؟
الجهات الفاعلة هي نفس الأشياء ، ولكن:
- على عكس الأشياء العادية ، لا يمكن للجهات الفاعلة استدعاء أساليب بعضهم البعض.
- يمكن للجهات الفاعلة نقل المعلومات فقط من خلال الرسائل غير القابلة للتغيير .
- عند استلام الرسالة ، يجوز للممثل
- إنشاء جهات فاعلة جديدة (ستكون أقل في التسلسل الهرمي) ،
- إرسال رسائل إلى الجهات الفاعلة الأخرى ،
- وقف الجهات الفاعلة أدناه في التسلسل الهرمي ونفسك.
لنلقِ نظرة على مثال.

الممثل A يريد إرسال رسالة إلى Actor B. كل ما لديه هو ActorRef (بعض العنوان). يمكن أن يكون الممثل B في أي مكان.
الممثل A يرسل خطاب B عبر النظام (ActorSystem). يضع النظام الرسالة في صندوق البريد الخاص بالممثل B والممثل "يستيقظ". يأخذ B. الممثل الخطاب من صندوق البريد ويقوم بشيء ما.
مقارنة بالطرق المستخدمة في كائن آخر ، يبدو الأمر معقدًا بشكل لا لزوم له ، لكن نموذج الممثلين يتناسب تمامًا في العالم الواقعي ، إذا كنت تتخيل أن الممثلين هم أشخاص مدربون على القيام بشيء ما استجابة لمنبهات معينة.
تخيل الأب والابن:

يرسل الأب ابنه SMSku "نظف في الغرفة" ويستمر في فعل شيء خاص به. الابن يقرأ SMSku ويبدأ التنظيف. الأب ، في هذه الأثناء ، يلعب البوكر. ينهي الابن عملية التنظيف ويرسل رسالة نصية قصيرة "إنهاء". يبدو بسيطا ، أليس كذلك؟
تخيل الآن أن الأب والابن ليسا ممثلين ، بل أشياء عادية يمكنها أن تسحب طرق بعضها البعض. يسحب الأب ابنه لطريقة "تنظيف الغرفة" ويتبع كعوبه ، منتظراً حتى ينتهي الابن من التنظيف ونقل السيطرة إلى والده. لا يستطيع الأب لعب البوكر في هذا الوقت. في هذا السياق ، أصبح نموذج الممثل أكثر جاذبية.
الآن دعنا ننتقل إلى
Akka.NET
كل ما هو مكتوب أدناه صحيح بالنسبة لـ Akka الأصلي لـ JVM ، لكن بالنسبة لي ، C # أقرب من Java ، لذلك سأستخدم Akka.NET كمثال.
فما هي فوائد عكا؟
- multithreading من خلال الرسائل. لم تعد مضطرًا إلى المعاناة مع كل أنواع الأقفال ، والإشارات ، والمزج ، وغيرها من السحر المميز للتعددية الكلاسيكية مع الذاكرة المشتركة.
- اتصال شفاف بين النظام ومكوناته. لا داعي للقلق بشأن رمز الشبكة المعقدة - سيجد النظام نفسه وجهة الرسالة ويضمن تسليم الرسالة (هنا يمكنك إدراج مزحة حول UDP مقابل TCP).
- بنية مرنة يمكن أن ترتفع أو تنخفض تلقائيًا. على سبيل المثال ، تحت الحمل ، يمكن للنظام رفع عقد عنقودية إضافية وتوزيع الحمل بالتساوي.
لكن موضوع التوسع واسع النطاق ويستحق إصدار منشور منفصل. لذلك ، سأقول بمزيد من التفاصيل فقط حول الميزة ، والتي ستكون مفيدة في جميع المشاريع:
خطأ في التعامل
الممثلون لديهم تسلسل هرمي - يمكن تمثيله كشجرة. كل ممثل لديه والد ، ويمكن أن يكون له "أطفال".
وثائق Akka.NET حقوق الطبع والنشر 2013-2018 مشروع Akka.NETلكل ممثل ، يمكنك وضع استراتيجية الإشراف - ما يجب القيام به إذا حدث خطأ ما "للأطفال". على سبيل المثال ، "تغلب" على ممثل لديه مشاكل ، ثم أنشئ ممثلًا جديدًا من نفس النوع وكلفه بنفس العمل.
على سبيل المثال ، تقدمت بطلب على Akka.net CRUD ، حيث يتم تطبيق طبقة "منطق الأعمال" على الجهات الفاعلة. كان الهدف من هذا المشروع هو معرفة ما إذا كان ينبغي استخدام الجهات الفاعلة في أنظمة غير قابلة للتطوير - هل ستجعل الحياة أفضل أو تضيف المزيد من الألم.
كيف يمكن أن تساعد معالجة الأخطاء المضمنة في Akka في:
- كل شيء على ما يرام ، يعمل التطبيق ،
- حدث شيء للمستودع ، والآن يعطي النتيجة مرة واحدة فقط من 5 ،
- قمت بتعيين استراتيجية الإشراف على "تجربة 10 مرات في الثانية" ،
- يعمل التطبيق مرة أخرى (وإن كان أبطأ) ، ولدي وقت لمعرفة ما هو الأمر.
هناك إغراء للقول: "هيا ، سأكتب مثل هذا الخطأ في التعامل مع نفسي ، لماذا يتعين على بعض الجهات الفاعلة أن ترتكب خطأ؟" ملاحظة عادلة ، ولكن فقط إذا كانت نقاط الفشل قليلة.
وبعض الرموز. هذه هي الطريقة التي يبدو بها التهيئة لنظام الممثل في حاوية IoC:
public Container() { system = ActorSystem.Create("MySystem"); var echo = system.ActorOf<EchoActor>("Echo");
EchoActor هو أبسط ممثل يقوم بإرجاع قيمة إلى المرسل:
public class EchoActor : ReceiveActor { public EchoActor() { Receive<bool>(flag => { Sender.Tell(flag); }); } }
لتوصيل الجهات الفاعلة بالكود "العادي" ، يتم استخدام الأمر Ask:
public async Task<ActionResult> Index() { ViewBag.Type = typeof(Model); var res = await CrudActorRef.Ask<IEnumerable<Model>>(DataMessage.GetAll<Model>(), maxDelay); return View(res); }
المجموع
سخرية من الجهات الفاعلة ، أستطيع أن أقول:
- انظر إليهم إذا كنت بحاجة إلى قابلية التوسع.
- بالنسبة لمنطق العمل المعقد ، من الأفضل عدم استخدامه بسبب
- حقن التبعية غريبة. لتهيئة ممثل مع التبعيات اللازمة ، يجب عليك أولاً إنشاء كائن Props ، ثم إعطائه إلى ActorSystem لإنشاء ممثل من النوع المطلوب. لإنشاء الدعائم باستخدام حاويات IoC (على سبيل المثال Castle Windsor أو Autofac) ، توجد أغلفة جاهزة - DependencyResolvers. لكنني واجهت حقيقة أن حاوية IoC كانت تحاول التحكم في عمر التبعية ، وبعد فترة سقط النظام بهدوء.
* ربما ، بدلاً من حقن التبعية في كائن ما ، يجب عليك وضع هذه التبعية كعنصر فاعل. - مشاكل الكتابة. ActorRef لا يعرف شيئًا عن نوع الممثل الذي يشير إليه. بمعنى أنه في وقت الترجمة ، لا يُعرف ما إذا كان بإمكان الممثل معالجة رسالة من هذا النوع أم لا.
الجزء 2: تيارات جيت
الآن دعنا ننتقل إلى موضوع أكثر شعبية ومفيدة - تدفقات الطائرات النفاثة. إذا لم تتمكن من مقابلة ممثلين في عملية العمل ، فستكون تدفقات Rx مفيدة بالتأكيد في الواجهة الأمامية والخلفية. تنفيذها في جميع لغات البرمجة الحديثة تقريبا. سأقدم أمثلة على RxJs ، لأنه حتى في هذه الأيام يجب على المبرمجين الخلفيين فعل شيء في JavaScript.
تتوفر تدفقات Rx لجميع لغات البرمجة الشائعة." مقدمة في البرمجة التفاعلية التي فقدتها " Andre Staltz ، المرخص لها بموجب CC BY-NC 4.0لشرح ماهية التدفق النفاث ، سأبدأ بمجموعات السحب والدفع.
| قيمة عودة واحدة | قيم عودة متعددة |
---|
سحب متزامن التفاعلية | ت | لا تعد ولا تحصى <T> |
دفع غير متزامن رد الفعل | المهمة <T> | IObservable <T> |
مجموعات السحب هي ما اعتدنا عليه جميعًا في البرمجة. المثال الأكثر لفتا هو مجموعة.
const arr = [1,2,3,4,5];
لديها بالفعل بيانات ، هو نفسه لن يغير هذه البيانات ، لكنه يمكنه تقديمها عند الطلب.
arr.forEach(console.log);
أيضًا ، قبل القيام بشيء ما باستخدام البيانات ، يمكنك معالجته بطريقة ما.
arr.map(i => i+1).map(I => “my number is ”+i).forEach(console.log);
الآن دعنا نتخيل أنه في البداية لا توجد بيانات في المجموعة ، لكنها بالتأكيد ستعلمك أنها ظهرت (Push). وفي الوقت نفسه ، لا يزال بإمكاننا تطبيق التحويلات الضرورية على هذه المجموعة.
على سبيل المثال:
source.map(i => i+1).map(I => “my number is ”+i).forEach(console.log);
عندما تظهر قيمة مثل 1 في المصدر ، ستخرج console.log "رقم هاتفي هو 1".
كيف يعمل:
يظهر كيان جديد - الموضوع (أو يمكن ملاحظته):
const observable = Rx.Observable.create(function (observer) { observer.next(1); observer.next(2); observer.next(3); setTimeout(() => { observer.next(4); observer.complete(); }, 1000); });
هذا عبارة عن مجموعة دفع ترسل إعلامات حول التغييرات في حالتها.
في هذه الحالة ، ستظهر الأرقام 1 و 2 و 3 على الفور ، في الثانية 4 ، ثم "ستنتهي المجموعة". هذا هو نوع خاص من الحدث.
الكيان الثاني هو المراقب. يمكنه الاشتراك في أحداث الموضوع والقيام بشيء مع البيانات المستلمة. على سبيل المثال:
observable.subscribe(x => console.log(x)); observable.subscribe({ next: x => console.log('got value ' + x), error: err => console.error('something wrong occurred: ' + err), complete: () => console.log('done'), }); observable .map(x => 'This is ' + x) .subscribe(x => console.log(x));
يمكن أن نرى أن موضوع واحد يمكن أن يكون له العديد من المشتركين.
يبدو الأمر سهلاً ، لكن لم يتضح بعد سبب ضرورة ذلك. سأقدم تعريفين آخرين تحتاج إلى معرفتهما عند العمل مع التدفقات التفاعلية ، ثم سأوضح في الممارسة العملية كيف تعمل وفي أي مواقف يتم الكشف عن كامل إمكاناتها.
ملاحظ الباردة
- يخطر الأحداث عندما يشترك شخص ما معهم.
- يتم إرسال دفق البيانات بأكمله مرة أخرى إلى كل مشترك ، بغض النظر عن وقت الاشتراك.
- يتم نسخ البيانات لكل مشترك.
ماذا يعني هذا: دعنا نقول أن الشركة (الموضوع) قررت ترتيب توزيع الهدايا. يأتي كل موظف (المراقب) إلى العمل ويستلم نسخته من الهدية. لا أحد يبقى محروما.
الملاحظات الساخنة
- يحاولون الإخطار بالحدث بغض النظر عن وجود المشتركين. إذا لم يكن هناك مشتركون في وقت الحدث ، فستفقد البيانات.
مثال: في الصباح ، يتم جلب الكعك الساخن للموظفين إلى الشركة. عندما يتم إحضارها ، تطير جميع القباب إلى الرائحة وتصنع الفطائر لتناول الإفطار. لكن البوم الذي جاء لاحقًا لم يعد يحصل على الفطائر.
في أي حالات يتم استخدام تدفقات الطائرات النفاثة؟
عندما يكون هناك دفق البيانات الموزعة مع مرور الوقت. على سبيل المثال ، إدخال المستخدم. أو سجلات من أي خدمة. في أحد المشاريع ، رأيت مُسجلاً عصاميًا قام بجمع الأحداث في ثوانٍ ، ثم سجل الحزمة بأكملها في وقت واحد. احتل رمز البطارية الصفحة. إذا تم استخدام تدفقات Rx ، فسيكون ذلك أبسط بكثير:
" RxJs Reference / Observable ، وثائق مرخصة بموجب CC BY 4.0 .
(هناك العديد من الأمثلة والصور التي توضح ما تقوم به العمليات المختلفة ذات التدفقات التفاعلية) source.bufferTime(2000).subsribe(doThings);
وأخيرا ، مثال على الاستخدام.
التعرف على إيماءات الماوس باستخدام تدفقات Rx
في الأوبرا القديمة أو خلفها الروحي - فيفالدي - كان هناك تحكم في المتصفح باستخدام إيماءات الماوس.
GIF - لفتات الماوس في فيفالدي أي أنك بحاجة إلى التعرف على حركات الماوس لأعلى / لأسفل ، لليمين / لليسار ، ومجموعات منها. يمكن كتابتها بدون تدفقات Rx ، لكن الكود سيكون معقدًا ويصعب صيانته.
وهنا هو ما يبدو مع تدفقات Rx:
سأبدأ من النهاية - سأقوم بتعيين البيانات وبأي تنسيق سأبحث فيه بالتسلسل الأصلي:
هذه هي ناقلات وحدة ومجموعاتهم.
بعد ذلك ، تحتاج إلى تحويل أحداث الماوس إلى تدفقات Rx. تحتوي جميع مكتبات Rx على أدوات مضمّنة لتحويل الأحداث القياسية إلى ملاحظات.
const mouseMoves = Rx.Observable.fromEvent(canvas, 'mousemove'), mouseDowns = Rx.Observable.fromEvent(canvas, 'mousedown'), mouseUps = Rx.Observable.fromEvent(canvas, 'mouseup');
بعد ذلك ، أقوم بتجميع إحداثيات الماوس بمقدار 2 وأجد اختلافها ، وأحصل على إزاحة الماوس.
const mouseDiffs = mouseMoves .map(getOffset) .pairwise() .map(pair => { return { x: pair[1].x-pair[0].x, y: pair[1].y-pair[0].y } });
وجمّع هذه الحركات باستخدام الأحداث "mousedown" و "mouseup".
const mouseGestures = mouseDiffs .bufferToggle(mouseDowns, x => mouseUps) .map(concat);
تعمل وظيفة concat على قطع الحركات القصيرة جدًا وحركات المجموعات التي يتم محاذاتها تقريبًا في الاتجاه.
function concat(values) {
إذا كانت الحركة على المحور X أو Y قصيرة جدًا ، تتم إعادة تعيينها إلى صفر. ثم تبقى العلامة فقط من إحداثيات الإزاحة التي تم الحصول عليها. وبالتالي ، يتم الحصول على متجهات الوحدة التي كنا نبحث عنها.
const normalizedMouseGestures = mouseGestures.map(arr => arr.map(v => { const dist = Math.hypot(vx, vy);
النتيجة:
gestures.map(gesture => normalizedMouseGestures.mergeMap( moves => Rx.Observable.from(moves) .sequenceEqual(gesture.sequence, comparer) ).filter(x => x).mapTo(gesture.name) ).mergeAll().subscribe(gestureName => actions[gestureName]());
باستخدام sequenceEqual ، يمكنك مقارنة الحركات المستلمة بالحركات الأصلية ، وإذا كان هناك تطابق ، فقم بإجراء معين.
→
يمكنك اللعب مع الإيماءات هنايرجى ملاحظة أنه بالإضافة إلى التعرف على الإيماءات ، هناك أيضًا رسم لكل من حركات الماوس الأولية والمطابقة على لوحة رسم HTML. قراءة التعليمات البرمجية لا تعاني من هذا.
من بينها ميزة أخرى تتبعها - يمكن إكمال الوظيفة المكتوبة بمساعدة تدفقات Rx وتوسيعها بسهولة.
ملخص
- تتوفر مكتبات بها تدفقات Rx لجميع لغات البرمجة تقريبًا.
- يجب استخدام تدفقات Rx عندما يكون هناك دفق من الأحداث ممتد عبر الوقت (على سبيل المثال ، إدخال المستخدم).
- يمكن استكمال الوظيفة المكتوبة باستخدام تدفقات Rx وتوسيعها بسهولة.
- لم أجد أي عيوب كبيرة.