سلسلة المسؤولية في قوالب متغيرة C ++

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

لنفترض أن لدينا كائنًا:

class Elephant { public: std::string touch_leg() { return "it's like a pillar"; } std::string touch_trunk() { return "it's like a snake"; } std::string touch_tail() { return "it's like a rope"; } void run_away() { m_is_gone = true; std::cout << "*** Sound of running out elephant ***\n"; } bool is_elephant_here() { return !m_is_gone; } private: bool m_is_gone = false; }; 

وسيكون لدينا 3 معالجات ، كل منها سيمرر الكائن أكثر:

 //        auto blind_sage3 = ChainOfRepsonsibility::start_new([](Elephant& e) { std::cout << "Third blind sage: " << e.touch_tail() << "\n"; }); // ""    ,     auto blind_sage2 = blind_sage3.attach([](Elephant& e, auto& next) { std::cout << "Second blind sage: " << e.touch_trunk() << "\n"; next(e); }); //    ,     ,    auto blind_sage1 = blind_sage2.attach([](Elephant& e, auto& next) { if (!e.is_elephant_here()) { std::cout << "First blind sage: So empty... so true\n"; } else { std::cout << "First blind sage: " << e.touch_leg() << "\n"; next(e); } }); //      Elephant e; blind_sage1(e); 

في هذا المثال ، هناك 3 معالجات ، كل منها عبارة عن تعبير لامدا ، والتي يتم دمجها في سلسلة من المسؤولية باستخدام فئة ChainOfRepsonsibility.

هنا تنفيذ الصف نفسه:

 #include <functional> struct ChainOfRepsonsibility { template<typename... Args> struct Chain { template<typename Callee, typename Next> Chain(const Callee c, const Next& n) { m_impl = c; m_next = n; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain(c, *this); } void operator()(Args... e) { m_impl(e..., m_next); } std::function<void(Args..., std::function<void(Args...)>)> m_impl; std::function<void(Args...)> m_next; }; template<typename... Args> struct ChainTail { template<typename Callee> ChainTail(Callee c) { m_impl = c; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain<Args...>(c, m_impl); } void operator()(Args... e) { m_impl(e...); } std::function<void(Args... e)> m_impl; }; template<typename> struct StartChain; template<typename C, typename... Args> struct StartChain<void (C::*)(Args...) const> { using Type = ChainTail<Args...>; }; template<typename Callee> static decltype(auto) start_new(Callee c) { return StartChain<decltype(&Callee::operator())>::Type(c); } }; 

يعمل مثل هذا:

  • أولاً ، ننشئ سلسلة من المسؤوليات باستخدام دالة start_new. تكمن المشكلة الرئيسية في هذه المرحلة في الحصول على قائمة بالحجج من لامدا التي تم تمريرها وإنشاء "نموذج أولي" للمعالج بناءً عليها.
  • للحصول على حجج لامدا ، يتم استخدام فئة StartChain وموضوع صعب مع التخصص في القوالب. أولاً ، نعلن عن متغير عام للفئة ، ثم بنية التخصص StartChain <void (C :: *) (Args ...) const> . يسمح لنا هذا البناء بالوصول إلى قائمة الحجج بواسطة عضو الفئة الذي تم تمريره.
  • من خلال وجود قائمة من الحجج ، يمكننا بالفعل إنشاء فئتي ChainTail و Chain ، والتي ستكون مجمعة للمعالجات. مهمتهم هي حفظ لامدا في وظيفة std :: واستدعائها في الوقت المناسب ، بالإضافة إلى تنفيذ طريقة الإرفاق (ضبط المعالج من الأعلى).

تم اختبار الرمز في visual studio 2015 ، بالنسبة إلى gcc ، قد يكون من الضروري إصلاح تخصص StartChain عن طريق إزالة الثابت من هناك ، إذا كنت ترغب في تمرير وظائف ، بالإضافة إلى lambdas ، يمكنك إضافة تخصص StartChain آخر. لا يزال بإمكانك محاولة التخلص من النسخ في مرفق ، قم بالتحرك ، ولكن لكي لا أعقد المثال ، تركت الحالة الأبسط فقط.

Source: https://habr.com/ru/post/ar413765/


All Articles